mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-11 23:54:18 +00:00
853 lines
32 KiB
C++
853 lines
32 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2015 - ROLI 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 AndroidStudioProjectExporter : public AndroidProjectExporterBase
|
|
{
|
|
public:
|
|
//==============================================================================
|
|
bool usesMMFiles() const override { return false; }
|
|
bool canCopeWithDuplicateFiles() override { return false; }
|
|
bool supportsUserDefinedConfigurations() const override { return false; }
|
|
|
|
bool isAndroidStudio() const override { return true; }
|
|
bool isAndroidAnt() const override { return false; }
|
|
|
|
static const char* getName() { return "Android Studio"; }
|
|
static const char* getValueTreeTypeName() { return "ANDROIDSTUDIO"; }
|
|
|
|
static AndroidStudioProjectExporter* createForSettings (Project& project, const ValueTree& settings)
|
|
{
|
|
if (settings.hasType (getValueTreeTypeName()))
|
|
return new AndroidStudioProjectExporter (project, settings);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//==============================================================================
|
|
CachedValue<String> gradleVersion, gradleWrapperVersion, gradleToolchain;
|
|
|
|
//==============================================================================
|
|
AndroidStudioProjectExporter (Project& p, const ValueTree& t)
|
|
: AndroidProjectExporterBase (p, t),
|
|
gradleVersion (settings, Ids::gradleVersion, nullptr, "2.10"),
|
|
gradleWrapperVersion (settings, Ids::gradleWrapperVersion, nullptr, "0.7.0-alpha4"),
|
|
gradleToolchain (settings, Ids::gradleToolchain, nullptr, "clang"),
|
|
androidStudioExecutable (findAndroidStudioExecutable())
|
|
{
|
|
name = getName();
|
|
|
|
if (getTargetLocationString().isEmpty())
|
|
getTargetLocationValue() = getDefaultBuildsRootFolder() + "AndroidStudio";
|
|
}
|
|
|
|
//==============================================================================
|
|
void createToolchainExporterProperties (PropertyListBuilder& props) override
|
|
{
|
|
props.add (new TextWithDefaultPropertyComponent<String> (gradleVersion, "gradle version", 32),
|
|
"The version of gradle that Android Studio should use to build this app");
|
|
|
|
props.add (new TextWithDefaultPropertyComponent<String> (gradleWrapperVersion, "gradle-experimental wrapper version", 32),
|
|
"The version of the gradle-experimental wrapper that Android Studio should use to build this app");
|
|
|
|
static const char* toolchains[] = { "clang", "gcc", nullptr };
|
|
|
|
props.add (new ChoicePropertyComponent (gradleToolchain.getPropertyAsValue(), "NDK Toolchain", StringArray (toolchains), Array<var> (toolchains)),
|
|
"The toolchain that gradle should invoke for NDK compilation (variable model.android.ndk.tooclhain in app/build.gradle)");
|
|
}
|
|
|
|
void createLibraryModuleExporterProperties (PropertyListBuilder&) override
|
|
{
|
|
// gradle cannot do native library modules as far as we know...
|
|
}
|
|
|
|
//==============================================================================
|
|
bool canLaunchProject() override
|
|
{
|
|
return androidStudioExecutable.exists();
|
|
}
|
|
|
|
bool launchProject() override
|
|
{
|
|
if (! androidStudioExecutable.exists())
|
|
{
|
|
jassertfalse;
|
|
return false;
|
|
}
|
|
|
|
const File targetFolder (getTargetFolder());
|
|
|
|
// we have to surround the path with extra quotes, otherwise Android Studio
|
|
// will choke if there are any space characters in the path.
|
|
return androidStudioExecutable.startAsProcess ("\"" + targetFolder.getFullPathName() + "\"");
|
|
}
|
|
|
|
Value getNDKPlatformVersionValue() { return getSetting (Ids::androidNdkPlatformVersion); }
|
|
String getNDKPlatformVersionString() const { return settings [Ids::androidNdkPlatformVersion]; }
|
|
|
|
Value getBuildToolsVersionValue() { return getSetting (Ids::buildToolsVersion); }
|
|
String getBuildToolsVersionString() const { return settings [Ids::buildToolsVersion]; }
|
|
|
|
//==============================================================================
|
|
void create (const OwnedArray<LibraryModule>& modules) const override
|
|
{
|
|
const File targetFolder (getTargetFolder());
|
|
|
|
removeOldFiles (targetFolder);
|
|
|
|
{
|
|
const String package (getActivityClassPackage());
|
|
const String path (package.replaceCharacter ('.', File::separator));
|
|
const File javaTarget (targetFolder.getChildFile ("app/src/main/java").getChildFile (path));
|
|
|
|
copyActivityJavaFiles (modules, javaTarget, package);
|
|
}
|
|
|
|
writeFile (targetFolder, "settings.gradle", getSettingsGradleFileContent());
|
|
writeFile (targetFolder, "build.gradle", getProjectBuildGradleFileContent());
|
|
writeFile (targetFolder, "app/build.gradle", getAppBuildGradleFileContent());
|
|
writeFile (targetFolder, "local.properties", getLocalPropertiesFileContent());
|
|
writeFile (targetFolder, "gradle/wrapper/gradle-wrapper.properties", getGradleWrapperPropertiesFileContent());
|
|
|
|
writeAndroidManifest (targetFolder);
|
|
writeStringsXML (targetFolder);
|
|
writeAppIcons (targetFolder);
|
|
createSourceSymlinks (targetFolder);
|
|
}
|
|
|
|
void removeOldFiles (const File& targetFolder) const
|
|
{
|
|
targetFolder.getChildFile ("app/src").deleteRecursively();
|
|
targetFolder.getChildFile ("app/build").deleteRecursively();
|
|
targetFolder.getChildFile ("app/build.gradle").deleteFile();
|
|
targetFolder.getChildFile ("gradle").deleteRecursively();
|
|
targetFolder.getChildFile ("local.properties").deleteFile();
|
|
targetFolder.getChildFile ("settings.gradle").deleteFile();
|
|
}
|
|
|
|
void writeFile (const File& gradleProjectFolder, const String& filePath, const String& fileContent) const
|
|
{
|
|
MemoryOutputStream outStream;
|
|
outStream << fileContent;
|
|
overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream);
|
|
}
|
|
|
|
//==============================================================================
|
|
static File findAndroidStudioExecutable()
|
|
{
|
|
#if JUCE_WINDOWS
|
|
const File defaultInstallation ("C:\\Program Files\\Android\\Android Studio\\bin");
|
|
|
|
if (defaultInstallation.exists())
|
|
{
|
|
{
|
|
const File studio64 = defaultInstallation.getChildFile ("studio64.exe");
|
|
|
|
if (studio64.existsAsFile())
|
|
return studio64;
|
|
}
|
|
|
|
{
|
|
const File studio = defaultInstallation.getChildFile ("studio.exe");
|
|
|
|
if (studio.existsAsFile())
|
|
return studio;
|
|
}
|
|
}
|
|
#elif JUCE_MAC
|
|
const File defaultInstallation ("/Applications/Android Studio.app");
|
|
|
|
if (defaultInstallation.exists())
|
|
return defaultInstallation;
|
|
#endif
|
|
|
|
return File::nonexistent;
|
|
}
|
|
|
|
protected:
|
|
//==============================================================================
|
|
class AndroidStudioBuildConfiguration : public BuildConfiguration
|
|
{
|
|
public:
|
|
AndroidStudioBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
|
|
: BuildConfiguration (p, settings, e)
|
|
{
|
|
if (getArchitectures().isEmpty())
|
|
{
|
|
if (isDebug())
|
|
getArchitecturesValue() = "armeabi x86";
|
|
else
|
|
getArchitecturesValue() = "armeabi armeabi-v7a x86";
|
|
}
|
|
}
|
|
|
|
Value getArchitecturesValue() { return getValue (Ids::androidArchitectures); }
|
|
String getArchitectures() const { return config [Ids::androidArchitectures]; }
|
|
|
|
var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); }
|
|
|
|
void createConfigProperties (PropertyListBuilder& props) override
|
|
{
|
|
addGCCOptimisationProperty (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 override
|
|
{
|
|
return new AndroidStudioBuildConfiguration (project, v, *this);
|
|
}
|
|
|
|
private:
|
|
static void createSymlinkAndParentFolders (const File& originalFile, const File& linkFile)
|
|
{
|
|
{
|
|
const File linkFileParentDirectory (linkFile.getParentDirectory());
|
|
|
|
// this will recursively creative the parent directories for the file.
|
|
// without this, the symlink would fail because it doesn't automatically create
|
|
// the folders if they don't exist
|
|
if (! linkFileParentDirectory.createDirectory())
|
|
throw SaveError (String ("Could not create directory ") + linkFileParentDirectory.getFullPathName());
|
|
}
|
|
|
|
if (! originalFile.createSymbolicLink (linkFile, true))
|
|
throw SaveError (String ("Failed to create symlink from ")
|
|
+ linkFile.getFullPathName() + " to "
|
|
+ originalFile.getFullPathName() + "!");
|
|
}
|
|
|
|
void makeSymlinksForGroup (const Project::Item& group, const File& targetFolder) const
|
|
{
|
|
if (! group.isGroup())
|
|
{
|
|
throw SaveError ("makeSymlinksForGroup was called with something other than a group!");
|
|
}
|
|
|
|
for (int i = 0; i < group.getNumChildren(); ++i)
|
|
{
|
|
const Project::Item& projectItem = group.getChild (i);
|
|
|
|
if (projectItem.isGroup())
|
|
{
|
|
makeSymlinksForGroup (projectItem, targetFolder.getChildFile (projectItem.getName()));
|
|
}
|
|
else if (projectItem.shouldBeAddedToTargetProject()) // must be a file then
|
|
{
|
|
const File originalFile (projectItem.getFile());
|
|
const File targetFile (targetFolder.getChildFile (originalFile.getFileName()));
|
|
|
|
createSymlinkAndParentFolders (originalFile, targetFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
void createSourceSymlinks (const File& folder) const
|
|
{
|
|
const File targetFolder (folder.getChildFile ("app/src/main/jni"));
|
|
|
|
// here we make symlinks to only to files included in the groups inside the project
|
|
// this is because Android Studio does not have a concept of groups and just uses
|
|
// the file system layout to determine what's to be compiled
|
|
{
|
|
const Array<Project::Item>& groups = getAllGroups();
|
|
|
|
for (int i = 0; i < groups.size(); ++i)
|
|
{
|
|
const Project::Item projectItem (groups.getReference (i));
|
|
const String projectItemName (projectItem.getName());
|
|
|
|
if (projectItem.isGroup())
|
|
makeSymlinksForGroup (projectItem, projectItemName == "Juce Modules" ? targetFolder.getChildFile ("JuceModules") : targetFolder);
|
|
}
|
|
}
|
|
}
|
|
|
|
void writeAppIcons (const File& folder) const
|
|
{
|
|
writeIcons (folder.getChildFile ("app/src/main/res/"));
|
|
}
|
|
|
|
static String sanitisePath (String path)
|
|
{
|
|
return expandHomeFolderToken (path).replace ("\\", "\\\\");
|
|
}
|
|
|
|
static String expandHomeFolderToken (const String& path)
|
|
{
|
|
String homeFolder = File::getSpecialLocation (File::userHomeDirectory).getFullPathName();
|
|
|
|
return path.replace ("${user.home}", homeFolder)
|
|
.replace ("~", homeFolder);
|
|
}
|
|
|
|
//==============================================================================
|
|
struct GradleElement
|
|
{
|
|
virtual ~GradleElement() {}
|
|
String toString() const { return toStringIndented (0); }
|
|
virtual String toStringIndented (int indentLevel) const = 0;
|
|
|
|
static String indent (int indentLevel) { return String::repeatedString (" ", indentLevel); }
|
|
};
|
|
|
|
//==============================================================================
|
|
struct GradleStatement : public GradleElement
|
|
{
|
|
GradleStatement (const String& s) : statement (s) {}
|
|
String toStringIndented (int indentLevel) const override { return indent (indentLevel) + statement; }
|
|
|
|
String statement;
|
|
};
|
|
|
|
//==============================================================================
|
|
struct GradleCppFlag : public GradleStatement
|
|
{
|
|
GradleCppFlag (const String& flag)
|
|
: GradleStatement ("cppFlags.add(" + flag.quoted() + ")") {}
|
|
};
|
|
|
|
struct GradlePreprocessorDefine : public GradleStatement
|
|
{
|
|
GradlePreprocessorDefine (const String& define, const String& value)
|
|
: GradleStatement ("cppFlags.add(\"-D" + define + "=" + value + "\")") {}
|
|
};
|
|
|
|
struct GradleHeaderIncludePath : public GradleStatement
|
|
{
|
|
GradleHeaderIncludePath (const String& path)
|
|
: GradleStatement ("cppFlags.add(\"-I" + sanitisePath (path) + "\".toString())") {}
|
|
};
|
|
|
|
struct GradleLibrarySearchPath : public GradleStatement
|
|
{
|
|
GradleLibrarySearchPath (const String& path)
|
|
: GradleStatement ("cppFlags.add(\"-L" + sanitisePath (path) + "\".toString())") {}
|
|
};
|
|
|
|
struct GradleLinkerFlag : public GradleStatement
|
|
{
|
|
GradleLinkerFlag (const String& flag)
|
|
: GradleStatement ("ldFlags.add(" + flag.quoted() + "\")") {}
|
|
};
|
|
|
|
struct GradleLinkLibrary : public GradleStatement
|
|
{
|
|
GradleLinkLibrary (const String& lib)
|
|
: GradleStatement ("ldLibs.add(" + lib.quoted() + ")") {}
|
|
};
|
|
|
|
//==============================================================================
|
|
struct GradleValue : public GradleElement
|
|
{
|
|
template <typename ValueType>
|
|
GradleValue (const String& k, const ValueType& v)
|
|
: key (k), value (v) {}
|
|
|
|
GradleValue (const String& k, bool boolValue)
|
|
: key (k), value (boolValue ? "true" : "false") {}
|
|
|
|
String toStringIndented (int indentLevel) const override
|
|
{
|
|
return indent (indentLevel) + key + " = " + value;
|
|
}
|
|
|
|
protected:
|
|
String key, value;
|
|
};
|
|
|
|
struct GradleString : public GradleValue
|
|
{
|
|
GradleString (const String& k, const String& str)
|
|
: GradleValue (k, str.quoted())
|
|
{
|
|
if (str.containsAnyOf ("${\"\'"))
|
|
value += ".toString()";
|
|
}
|
|
};
|
|
|
|
struct GradleFilePath : public GradleValue
|
|
{
|
|
GradleFilePath (const String& k, const String& path)
|
|
: GradleValue (k, "new File(\"" + sanitisePath (path) + "\")") {}
|
|
};
|
|
|
|
//==============================================================================
|
|
struct GradleObject : public GradleElement
|
|
{
|
|
GradleObject (const String& nm) : name (nm) {}
|
|
|
|
#if JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES
|
|
template <typename GradleType, typename... Args>
|
|
void add (Args... args)
|
|
{
|
|
children.add (new GradleType (args...));
|
|
// Note: can't use std::forward because it doesn't compile for OS X 10.8
|
|
}
|
|
#else // Remove this workaround once we drop VS2012 support!
|
|
template <typename GradleType, typename Arg1>
|
|
void add (Arg1 arg1)
|
|
{
|
|
children.add (new GradleType (arg1));
|
|
}
|
|
|
|
template <typename GradleType, typename Arg1, typename Arg2>
|
|
void add (Arg1 arg1, Arg2 arg2)
|
|
{
|
|
children.add (new GradleType (arg1, arg2));
|
|
}
|
|
#endif
|
|
|
|
void addChildObject (GradleObject* objectToAdd) noexcept
|
|
{
|
|
children.add (objectToAdd);
|
|
}
|
|
|
|
String toStringIndented (int indentLevel) const override
|
|
{
|
|
String result;
|
|
result << indent (indentLevel) << name << " {" << newLine;
|
|
|
|
for (const auto& child : children)
|
|
result << child->toStringIndented (indentLevel + 1) << newLine;
|
|
|
|
result << indent (indentLevel) << "}";
|
|
|
|
if (indentLevel == 0)
|
|
result << newLine;
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
String name;
|
|
OwnedArray<GradleElement> children;
|
|
};
|
|
|
|
//==============================================================================
|
|
String getSettingsGradleFileContent() const
|
|
{
|
|
return "include ':app'";
|
|
}
|
|
|
|
String getProjectBuildGradleFileContent() const
|
|
{
|
|
String projectBuildGradle;
|
|
projectBuildGradle << getGradleBuildScript();
|
|
projectBuildGradle << getGradleAllProjects();
|
|
|
|
return projectBuildGradle;
|
|
}
|
|
|
|
//==============================================================================
|
|
String getGradleBuildScript() const
|
|
{
|
|
GradleObject buildScript ("buildscript");
|
|
|
|
buildScript.addChildObject (getGradleRepositories());
|
|
buildScript.addChildObject (getGradleDependencies());
|
|
|
|
return buildScript.toString();
|
|
}
|
|
|
|
GradleObject* getGradleRepositories() const
|
|
{
|
|
auto repositories = new GradleObject ("repositories");
|
|
repositories->add<GradleStatement> ("jcenter()");
|
|
return repositories;
|
|
}
|
|
|
|
GradleObject* getGradleDependencies() const
|
|
{
|
|
auto dependencies = new GradleObject ("dependencies");
|
|
|
|
dependencies->add<GradleStatement> ("classpath 'com.android.tools.build:gradle-experimental:"
|
|
+ gradleWrapperVersion.get() + "'");
|
|
return dependencies;
|
|
}
|
|
|
|
String getGradleAllProjects() const
|
|
{
|
|
GradleObject allProjects ("allprojects");
|
|
allProjects.addChildObject (getGradleRepositories());
|
|
return allProjects.toString();
|
|
}
|
|
|
|
//==============================================================================
|
|
String getAppBuildGradleFileContent() const
|
|
{
|
|
String appBuildGradle;
|
|
|
|
appBuildGradle << "apply plugin: 'com.android.model.application'" << newLine;
|
|
appBuildGradle << getAndroidModel();
|
|
appBuildGradle << getAppDependencies();
|
|
|
|
return appBuildGradle;
|
|
}
|
|
|
|
String getAndroidModel() const
|
|
{
|
|
GradleObject model ("model");
|
|
|
|
model.addChildObject (getAndroidObject());
|
|
model.addChildObject (getAndroidNdkSettings());
|
|
model.addChildObject (getAndroidSources());
|
|
model.addChildObject (getAndroidBuildConfigs());
|
|
model.addChildObject (getAndroidSigningConfigs());
|
|
model.addChildObject (getAndroidProductFlavours());
|
|
|
|
return model.toString();
|
|
}
|
|
|
|
String getAppDependencies() const
|
|
{
|
|
GradleObject dependencies ("dependencies");
|
|
dependencies.add<GradleStatement> ("compile \"com.android.support:support-v4:+\"");
|
|
return dependencies.toString();
|
|
}
|
|
|
|
//==============================================================================
|
|
GradleObject* getAndroidObject() const
|
|
{
|
|
auto android = new GradleObject ("android");
|
|
|
|
android->add<GradleValue> ("compileSdkVersion", androidMinimumSDK.get().getIntValue());
|
|
android->add<GradleString> ("buildToolsVersion", getBuildToolsVersionString());
|
|
android->addChildObject (getAndroidDefaultConfig());
|
|
|
|
return android;
|
|
}
|
|
|
|
GradleObject* getAndroidDefaultConfig() const
|
|
{
|
|
const String bundleIdentifier = project.getBundleIdentifier().toString().toLowerCase();
|
|
const int minSdkVersion = androidMinimumSDK.get().getIntValue();
|
|
|
|
auto defaultConfig = new GradleObject ("defaultConfig.with");
|
|
|
|
defaultConfig->add<GradleString> ("applicationId", bundleIdentifier);
|
|
defaultConfig->add<GradleValue> ("minSdkVersion.apiLevel", minSdkVersion);
|
|
defaultConfig->add<GradleValue> ("targetSdkVersion.apiLevel", minSdkVersion);
|
|
|
|
return defaultConfig;
|
|
}
|
|
|
|
GradleObject* getAndroidNdkSettings() const
|
|
{
|
|
const String toolchain = gradleToolchain.get();
|
|
const bool isClang = (toolchain == "clang");
|
|
|
|
auto ndkSettings = new GradleObject ("android.ndk");
|
|
|
|
ndkSettings->add<GradleString> ("moduleName", "juce_jni");
|
|
ndkSettings->add<GradleString> ("toolchain", toolchain);
|
|
|
|
if (isClang)
|
|
{
|
|
ndkSettings->add<GradleString> ("stl", "c++_static");
|
|
}
|
|
else
|
|
{
|
|
ndkSettings->add<GradleValue> ("toolchainVersion", "4.9");
|
|
ndkSettings->add<GradleString> ("stl", "gnustl_static");
|
|
}
|
|
|
|
ndkSettings->addChildObject (getNdkJuceExtraProperties());
|
|
|
|
addAllNdkCompilerSettings (ndkSettings);
|
|
|
|
return ndkSettings;
|
|
}
|
|
|
|
GradleObject* getNdkJuceExtraProperties() const
|
|
{
|
|
auto ext = new GradleObject ("ext");
|
|
ext->add<GradleString> ("juceRootDir", "${project.rootDir}/../../../../");
|
|
ext->add<GradleString> ("juceModuleDir", "${juceRootDir}/modules");
|
|
return ext;
|
|
}
|
|
|
|
void addAllNdkCompilerSettings (GradleObject* ndk) const
|
|
{
|
|
addNdkCppFlags (ndk);
|
|
addNdkPreprocessorDefines (ndk);
|
|
addNdkHeaderIncludePaths (ndk);
|
|
addNdkLinkerFlags (ndk);
|
|
addNdkLibraries (ndk);
|
|
}
|
|
|
|
void addNdkCppFlags (GradleObject* ndk) const
|
|
{
|
|
const char* alwaysUsedFlags[] = { "-fsigned-char", "-fexceptions", "-frtti", "-std=c++11", nullptr };
|
|
StringArray cppFlags (alwaysUsedFlags);
|
|
|
|
cppFlags.mergeArray (StringArray::fromTokens (getExtraCompilerFlagsString(), " ", ""));
|
|
|
|
for (int i = 0; i < cppFlags.size(); ++i)
|
|
ndk->add<GradleCppFlag> (cppFlags[i]);
|
|
}
|
|
|
|
void addNdkPreprocessorDefines (GradleObject* ndk) const
|
|
{
|
|
const auto& defines = getAllPreprocessorDefs();
|
|
|
|
for (int i = 0; i < defines.size(); ++i)
|
|
ndk->add<GradlePreprocessorDefine> ( defines.getAllKeys()[i], defines.getAllValues()[i]);
|
|
}
|
|
|
|
void addNdkHeaderIncludePaths (GradleObject* ndk) const
|
|
{
|
|
const char* basicJucePaths[] = { "${project.rootDir}/app", "${ext.juceRootDir}", "${ext.juceModuleDir}", nullptr };
|
|
StringArray includePaths (basicJucePaths);
|
|
|
|
auto cppFiles = getAllIncludedCppFiles();
|
|
|
|
for (const auto& cppFile : cppFiles)
|
|
includePaths.addIfNotAlreadyThere (getIncludePathForFile (cppFile));
|
|
|
|
for (const auto& path : includePaths)
|
|
ndk->add<GradleHeaderIncludePath> (path);
|
|
}
|
|
|
|
Array<RelativePath> getAllIncludedCppFiles() const
|
|
{
|
|
Array<RelativePath> cppFiles;
|
|
const auto& groups = getAllGroups();
|
|
|
|
for (int i = 0; i < groups.size(); ++i)
|
|
findAllProjectItemsWithPredicate (groups.getReference (i), cppFiles, ShouldBeAddedToProjectPredicate());
|
|
|
|
return cppFiles;
|
|
}
|
|
|
|
String getIncludePathForFile (const RelativePath& file) const
|
|
{
|
|
return sanitisePath (project.getProjectFolder().getFullPathName() + "/"
|
|
+ file.rebased (getTargetFolder(),
|
|
project.getProjectFolder(),
|
|
RelativePath::projectFolder)
|
|
.toUnixStyle()
|
|
.upToLastOccurrenceOf ("/", false, false));
|
|
}
|
|
|
|
void addNdkLinkerFlags (GradleObject* ndk) const
|
|
{
|
|
const auto linkerFlags = StringArray::fromTokens (getExtraLinkerFlagsString(), " ", "");
|
|
|
|
for (const auto& flag : linkerFlags)
|
|
ndk->add<GradleLinkerFlag> (flag);
|
|
|
|
}
|
|
void addNdkLibraries (GradleObject* ndk) const
|
|
{
|
|
const char* requiredAndroidLibs[] = { "android", "EGL", "GLESv2", "log", nullptr };
|
|
StringArray libs (requiredAndroidLibs);
|
|
|
|
libs.addArray (StringArray::fromTokens(getExternalLibrariesString(), ";", ""));
|
|
|
|
for (const auto& lib : libs)
|
|
ndk->add<GradleLinkLibrary> (lib);
|
|
}
|
|
|
|
GradleObject* getAndroidSources() const
|
|
{
|
|
auto source = new GradleObject ("source"); // app source folder
|
|
source->add<GradleStatement> ("exclude \"**/JuceModules/\"");
|
|
|
|
auto jni = new GradleObject ("jni"); // all C++ sources for app
|
|
jni->addChildObject (source);
|
|
|
|
auto main = new GradleObject ("main"); // all sources for app
|
|
main->addChildObject (jni);
|
|
|
|
auto sources = new GradleObject ("android.sources"); // all sources
|
|
sources->addChildObject (main);
|
|
return sources;
|
|
}
|
|
|
|
GradleObject* getAndroidBuildConfigs() const
|
|
{
|
|
auto buildConfigs = new GradleObject ("android.buildTypes");
|
|
|
|
for (ConstConfigIterator config (*this); config.next();)
|
|
buildConfigs->addChildObject (getBuildConfig (*config));
|
|
|
|
return buildConfigs;
|
|
}
|
|
|
|
GradleObject* getBuildConfig (const BuildConfiguration& config) const
|
|
{
|
|
const String configName (config.getName());
|
|
|
|
// Note: at the moment, Android Studio only supports a "debug" and a "release"
|
|
// build config, but no custom build configs like Projucer's other exporters do.
|
|
if (configName != "Debug" && configName != "Release")
|
|
throw SaveError ("Build configurations other than Debug and Release are not yet support for Android Studio");
|
|
|
|
auto gradleConfig = new GradleObject (configName.toLowerCase());
|
|
|
|
if (! config.isDebug())
|
|
gradleConfig->add<GradleValue> ("signingConfig", "$(\"android.signingConfigs.releaseConfig\")");
|
|
|
|
addConfigNdkSettings (gradleConfig, config);
|
|
|
|
return gradleConfig;
|
|
}
|
|
|
|
void addConfigNdkSettings (GradleObject* buildConfig, const BuildConfiguration& config) const
|
|
{
|
|
auto ndkSettings = new GradleObject ("ndk.with");
|
|
|
|
if (config.isDebug())
|
|
{
|
|
ndkSettings->add<GradleValue> ("debuggable", true);
|
|
ndkSettings->add<GradleCppFlag> ("-g");
|
|
ndkSettings->add<GradlePreprocessorDefine> ("DEBUG", "1");
|
|
ndkSettings->add<GradlePreprocessorDefine> ("_DEBUG", "1");
|
|
}
|
|
else
|
|
{
|
|
ndkSettings->add<GradlePreprocessorDefine> ("NDEBUG", "1");
|
|
}
|
|
|
|
ndkSettings->add<GradleCppFlag> ("-O" + config.getGCCOptimisationFlag());
|
|
|
|
for (const auto& path : config.getHeaderSearchPaths())
|
|
ndkSettings->add<GradleHeaderIncludePath> (path);
|
|
|
|
for (const auto& path : config.getLibrarySearchPaths())
|
|
ndkSettings->add<GradleLibrarySearchPath> (path);
|
|
|
|
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID", "1");
|
|
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID_API_VERSION", androidMinimumSDK.get());
|
|
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_'));
|
|
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID_ACTIVITY_CLASSPATH","\\\"" + androidActivityClass.get().replaceCharacter('.', '/') + "\\\"");
|
|
|
|
const auto defines = config.getAllPreprocessorDefs();
|
|
for (int i = 0; i < defines.size(); ++i)
|
|
ndkSettings->add<GradlePreprocessorDefine> (defines.getAllKeys()[i], defines.getAllValues()[i]);
|
|
|
|
buildConfig->addChildObject (ndkSettings);
|
|
}
|
|
|
|
GradleObject* getAndroidSigningConfigs() const
|
|
{
|
|
auto releaseConfig = new GradleObject ("create(\"releaseConfig\")");
|
|
|
|
releaseConfig->add<GradleFilePath> ("storeFile", androidKeyStore.get());
|
|
releaseConfig->add<GradleString> ("storePassword", androidKeyStorePass.get());
|
|
releaseConfig->add<GradleString> ("keyAlias", androidKeyAlias.get());
|
|
releaseConfig->add<GradleString> ("keyPassword", androidKeyAliasPass.get());
|
|
releaseConfig->add<GradleString> ("storeType", "jks");
|
|
|
|
auto signingConfigs = new GradleObject ("android.signingConfigs");
|
|
|
|
signingConfigs->addChildObject (releaseConfig);
|
|
// Note: no need to add a debugConfig, Android Studio will use debug.keystore by default
|
|
|
|
return signingConfigs;
|
|
}
|
|
|
|
GradleObject* getAndroidProductFlavours() const
|
|
{
|
|
auto flavours = new GradleObject ("android.productFlavors");
|
|
|
|
StringArray architectures (StringArray::fromTokens (getABIs<AndroidStudioBuildConfiguration> (true), " ", ""));
|
|
architectures.mergeArray (StringArray::fromTokens (getABIs<AndroidStudioBuildConfiguration> (false), " ", ""));
|
|
|
|
if (architectures.size() == 0)
|
|
throw SaveError ("Can't build for no architectures!");
|
|
|
|
for (int i = 0; i < architectures.size(); ++i)
|
|
{
|
|
String arch (architectures[i].trim());
|
|
|
|
if ((arch).isEmpty())
|
|
continue;
|
|
|
|
flavours->addChildObject (getGradleProductFlavourForArch (arch));
|
|
}
|
|
|
|
return flavours;
|
|
}
|
|
|
|
GradleObject* getGradleProductFlavourForArch (const String& arch) const
|
|
{
|
|
auto flavour = new GradleObject ("create(\"" + arch + "\")");
|
|
flavour->add<GradleStatement> ("ndk.abiFilters.add(\"" + arch + "\")");
|
|
return flavour;
|
|
}
|
|
//==============================================================================
|
|
String getLocalPropertiesFileContent() const
|
|
{
|
|
String props;
|
|
|
|
props << "ndk.dir=" << sanitisePath (ndkPath.toString()) << newLine
|
|
<< "sdk.dir=" << sanitisePath (sdkPath.toString()) << newLine;
|
|
|
|
return props;
|
|
}
|
|
|
|
String getGradleWrapperPropertiesFileContent() const
|
|
{
|
|
String props;
|
|
|
|
props << "distributionUrl=https\\://services.gradle.org/distributions/gradle-"
|
|
<< gradleVersion.get() << "-all.zip";
|
|
|
|
return props;
|
|
}
|
|
|
|
//==============================================================================
|
|
void writeStringsXML (const File& folder) const
|
|
{
|
|
XmlElement strings ("resources");
|
|
XmlElement* resourceName = strings.createNewChildElement ("string");
|
|
|
|
resourceName->setAttribute ("name", "app_name");
|
|
resourceName->addTextElement (projectName);
|
|
|
|
writeXmlOrThrow (strings, folder.getChildFile ("app/src/main/res/values/string.xml"), "utf-8", 100, true);
|
|
}
|
|
|
|
//==============================================================================
|
|
void writeAndroidManifest (const File& folder) const
|
|
{
|
|
ScopedPointer<XmlElement> manifest (createManifestXML());
|
|
|
|
writeXmlOrThrow (*manifest, folder.getChildFile ("app/src/main/AndroidManifest.xml"), "utf-8", 100, true);
|
|
}
|
|
|
|
//==============================================================================
|
|
struct ShouldBeAddedToProjectPredicate
|
|
{
|
|
bool operator() (const Project::Item& projectItem) const { return projectItem.shouldBeAddedToTargetProject(); }
|
|
};
|
|
|
|
//==============================================================================
|
|
const File androidStudioExecutable;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (AndroidStudioProjectExporter)
|
|
};
|