|
- /*
- ==============================================================================
-
- 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:
- //==============================================================================
- 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;
- }
-
- //==============================================================================
- AndroidStudioProjectExporter (Project& p, const ValueTree& t)
- : AndroidProjectExporterBase (p, t),
- androidStudioExecutable (findAndroidStudioExecutable())
- {
- name = getName();
-
- if (getTargetLocationString().isEmpty())
- getTargetLocationValue() = getDefaultBuildsRootFolder() + "AndroidStudio";
- }
-
- //==============================================================================
- 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() + "\"");
- }
-
- void createExporterProperties (PropertyListBuilder& props) override
- {
- AndroidProjectExporterBase::createExporterProperties (props);
-
- props.add (new TextPropertyComponent (getNDKPlatformVersionValue(), "NDK Platform Version", 32, false),
- "The value to use for android$user.ndk.platformVersion in Gradle");
-
- props.add (new TextPropertyComponent (getBuildToolsVersionValue(), "Build Tools Version", 32, false),
- "The version of build tools use for build tools in Gradle");
- }
-
- 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 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 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);
- }
-
- writeSettingsDotGradle (targetFolder);
- writeLocalDotProperties (targetFolder);
- writeBuildDotGradleRoot (targetFolder);
- writeBuildDotGradleApp (targetFolder);
- writeGradleWrapperProperties (targetFolder);
- writeAndroidManifest (targetFolder);
- writeStringsXML (targetFolder);
- writeAppIcons (targetFolder);
-
- createSourceSymlinks (targetFolder);
- }
-
- 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 createSymboicLinkAndCreateParentFolders (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()));
-
- createSymboicLinkAndCreateParentFolders (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/"));
- }
-
- void writeSettingsDotGradle (const File& folder) const
- {
- MemoryOutputStream memoryOutputStream;
-
- memoryOutputStream << "include ':app'";
-
- overwriteFileIfDifferentOrThrow (folder.getChildFile ("settings.gradle"), memoryOutputStream);
- }
-
- 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);
- }
-
- void writeLocalDotProperties (const File& folder) const
- {
- MemoryOutputStream memoryOutputStream;
-
- memoryOutputStream << "ndk.dir=" << sanitisePath (getNDKPathString()) << newLine
- << "sdk.dir=" << sanitisePath (getSDKPathString());
-
- overwriteFileIfDifferentOrThrow (folder.getChildFile ("local.properties"), memoryOutputStream);
- }
-
- void writeGradleWrapperProperties (const File& folder) const
- {
- MemoryOutputStream memoryOutputStream;
-
- memoryOutputStream << "distributionUrl=https\\://services.gradle.org/distributions/gradle-2.6-all.zip";
-
- overwriteFileIfDifferentOrThrow (folder.getChildFile ("gradle/wrapper/gradle-wrapper.properties"), memoryOutputStream);
- }
-
- void writeBuildDotGradleRoot (const File& folder) const
- {
- MemoryOutputStream memoryOutputStream;
-
- const String indent = getIndentationString();
-
- // this is needed to make sure the correct version of
- // the gradle build tools is available
- // otherwise, the user will get an error about
- // com.android.tools.something not being available
- memoryOutputStream << "buildscript {" << newLine
- << indent << "repositories {" << newLine
- << indent << indent << "jcenter()" << newLine
- << indent << "}" << newLine
- << indent << "dependencies {" << newLine
- << indent << indent << "classpath 'com.android.tools.build:gradle-experimental:0.3.0-alpha7'" << newLine
- << indent << "}" << newLine
- << "}" << newLine
- << newLine
- << "allprojects {" << newLine
- << indent << "repositories {" << newLine
- << indent << indent << "jcenter()" << newLine
- << indent << "}" << newLine
- << "}";
-
- overwriteFileIfDifferentOrThrow (folder.getChildFile ("build.gradle"), memoryOutputStream);
- }
-
- 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);
- }
-
- String createModelDotAndroid (const String& indent,
- const String& minimumSDKVersion,
- const String& buildToolsVersion,
- const String& bundleIdentifier) const
- {
- String result;
-
- result << "android {" << newLine
- << indent << "compileSdkVersion = " << minimumSDKVersion << newLine
- << indent << "buildToolsVersion = \"" << buildToolsVersion << "\"" << newLine
- << indent << "defaultConfig.with {" << newLine
- << indent << indent << "applicationId = \"" << bundleIdentifier.toLowerCase() << "\"" << newLine
- << indent << indent << "minSdkVersion.apiLevel = " << minimumSDKVersion << newLine
- << indent << indent << "targetSdkVersion.apiLevel = " << minimumSDKVersion << newLine
- << indent << "}" << newLine
- << "}" << newLine;
-
- return result;
- }
-
- String createModelDotCompileOptions (const String& indent) const
- {
- String result;
-
- result << "compileOptions.with {" << newLine
- << indent << "sourceCompatibility = JavaVersion.VERSION_1_7" << newLine
- << indent << indent << "targetCompatibility = JavaVersion.VERSION_1_7" << newLine
- << "}" << newLine;
-
- return result;
- }
-
- String createModelDotAndroidSources (const String& indent) const
- {
- String result;
-
- result << "android.sources {" << newLine
- << indent << "main {" << newLine
- << indent << indent << "jni {" << newLine
- << indent << indent << indent << "source {" << newLine
- << indent << indent << indent << indent << "exclude \"**/JuceModules/\"" << newLine
- << indent << indent << indent << "}" << newLine
- << indent << indent << "}" << newLine
- << indent << "}" << newLine
- << "}" << newLine;
-
- return result;
- }
-
- struct ShouldBeAddedToProjectPredicate
- {
- bool operator() (const Project::Item& projectItem) const { return projectItem.shouldBeAddedToTargetProject(); }
- };
-
- StringArray getCPPFlags() const
- {
- StringArray result;
-
- result.add ("\"-fsigned-char\"");
- result.add ("\"-fexceptions\"");
- result.add ("\"-frtti\"");
-
- if (isCPP11Enabled())
- result.add ("\"-std=gnu++11\"");
-
- // preprocessor definitions
-
- {
- StringPairArray preprocessorDefinitions = getAllPreprocessorDefs();
- preprocessorDefinitions.set ("JUCE_ANDROID", "1");
- preprocessorDefinitions.set ("JUCE_ANDROID_API_VERSION", getMinimumSDKVersionString());
- preprocessorDefinitions.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_'));
- preprocessorDefinitions.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\\\"" + getActivityClassPath().replaceCharacter('.', '/') + "\\\"");
-
- const StringArray& keys = preprocessorDefinitions.getAllKeys();
-
- for (int i = 0; i < keys.size(); ++i)
- result.add (String ("\"-D") + keys[i] + String ("=") + preprocessorDefinitions[keys[i]] + "\"");
- }
-
- // include paths
-
- result.add ("\"-I${project.rootDir}/app\".toString()");
- result.add ("\"-I${ext.juceRootDir}\".toString()");
- result.add ("\"-I${ext.juceModuleDir}\".toString()");
-
- {
- Array<RelativePath> cppFiles;
- const Array<Project::Item>& groups = getAllGroups();
-
- for (int i = 0; i < groups.size(); ++i)
- findAllProjectItemsWithPredicate (groups.getReference (i), cppFiles, ShouldBeAddedToProjectPredicate());
-
- for (int i = 0; i < cppFiles.size(); ++i)
- {
- const RelativePath absoluteSourceFile (cppFiles.getReference (i).rebased (getTargetFolder(),
- project.getProjectFolder(),
- RelativePath::projectFolder));
-
- const String absoluteIncludeFolder (sanitisePath (project.getProjectFolder().getFullPathName() + "/"
- + absoluteSourceFile.toUnixStyle().upToLastOccurrenceOf ("/", false, false)));
-
- result.addIfNotAlreadyThere ("\"-I" + absoluteIncludeFolder + "\".toString()");
- }
- }
-
- return result;
- }
-
- StringArray getLDLibs() const
- {
- StringArray result;
-
- result.add ("android");
- result.add ("EGL");
- result.add ("GLESv2");
- result.add ("log");
-
- result.addArray (StringArray::fromTokens(getExternalLibrariesString(), ";", ""));
-
- return result;
- }
-
- String createModelDotAndroidNDK (const String& indent) const
- {
- String result;
- const String platformVersion (getNDKPlatformVersionString());
-
- result << "android.ndk {" << newLine
- << indent << "moduleName = \"juce_jni\"" << newLine
- << indent << "stl = \"gnustl_static\"" << newLine
- << indent << "toolchainVersion = 4.9" << newLine;
-
- if (platformVersion.isNotEmpty())
- result << indent << "platformVersion = " << getNDKPlatformVersionString() << newLine;
-
- result << indent << "ext {" << newLine
- << indent << indent << "juceRootDir = \"" << "${project.rootDir}/../../../../" << "\".toString()" << newLine
- << indent << indent << "juceModuleDir = \"" << "${juceRootDir}/modules" << "\".toString()" << newLine
- << indent << "}" << newLine;
-
- // CPP flags
-
- {
- StringArray cppFlags (getCPPFlags());
-
- for (int i = 0; i < cppFlags.size(); ++i)
- result << indent << "cppFlags += " << cppFlags[i] << newLine;
- }
-
- // libraries
-
- {
- StringArray libraries (getLDLibs());
-
- result << indent << "ldLibs += [";
-
- for (int i = 0; i < libraries.size(); ++i)
- {
- result << "\"" << libraries[i] << "\"";
-
- if (i + 1 != libraries.size())
- result << ", ";
- }
-
- result << "]" << newLine;
- }
-
- result << "}" << newLine;
-
- return result;
- }
-
- String getGradleCPPFlags (const String& indent, const ConstConfigIterator& config) const
- {
- String result;
- StringArray rootFlags;
- StringArray ndkFlags;
-
- if (config->isDebug())
- {
- ndkFlags.add ("debuggable = true");
- ndkFlags.add ("cppFlags += \"-g\"");
- ndkFlags.add ("cppFlags += \"-DDEBUG=1\"");
- ndkFlags.add ("cppFlags += \"-D_DEBUG=1\"");
- }
- else
- {
- rootFlags.add ("minifyEnabled = true");
- rootFlags.add ("proguardFiles += 'proguard-android-optimize.txt'");
-
- ndkFlags.add ("cppFlags += \"-DNDEBUG=1\"");
- }
-
- {
- StringArray extraFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), " ", ""));
-
- for (int i = 0; extraFlags.size(); ++i)
- ndkFlags.add (String ("cppFlags += \"") + extraFlags[i] + "\"");
- }
-
- // there appears to be an issue with build types that have a name other than
- // "debug" or "release". Apparently this is hard coded in Android Studio ...
- {
- const String configName (config->getName());
-
- if (configName != "Debug" && configName != "Release")
- throw SaveError ("Build configurations other than Debug and Release are not yet support for Android Studio");
-
- result << configName.toLowerCase() << " {" << newLine;
- }
-
- for (int i = 0; i < rootFlags.size(); ++i)
- result << indent << rootFlags[i] << newLine;
-
- result << indent << "ndk.with {" << newLine;
-
- for (int i = 0; i < ndkFlags.size(); ++i)
- result << indent << indent << ndkFlags[i] << newLine;
-
- result << indent << "}" << newLine
- << "}" << newLine;
-
- return result;
- }
-
- String createModelDotAndroidDotBuildTypes (const String& indent) const
- {
- String result;
-
- result << "android.buildTypes {" << newLine;
-
- for (ConstConfigIterator config (*this); config.next();)
- result << CodeHelpers::indent (getGradleCPPFlags (indent, config), indent.length(), true);
-
- result << "}";
-
- return result;
- }
-
- String createModelDotAndroidDotProductFlavors (const String& indent) const
- {
- String result;
-
- result << "android.productFlavors {" << newLine;
-
- // TODO! - this needs to be changed so that it generates seperate flags for debug and release ...
- // at present, it just generates all ABIs for all build types
-
- 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 architecture (architectures[i].trim());
-
- if (architecture.isEmpty())
- continue;
-
- result << indent << "create(\"" << architecture << "\") {" << newLine
- << indent << indent << "ndk.abiFilters += \"" << architecture << "\"" << newLine
- << indent << "}" << newLine;
- }
-
- result << "}" << newLine;
-
- return result;
- }
-
- void writeBuildDotGradleApp (const File& folder) const
- {
- MemoryOutputStream memoryOutputStream;
-
- const String indent = getIndentationString();
- const String minimumSDKVersion = getMinimumSDKVersionString();
- const String bundleIdentifier = project.getBundleIdentifier().toString();
-
- String buildToolsVersion = getBuildToolsVersionString();
-
- if (buildToolsVersion.isEmpty())
- buildToolsVersion = "23.0.1";
-
- memoryOutputStream << "apply plugin: 'com.android.model.application'" << newLine
- << newLine
- << "model {" << newLine
- << CodeHelpers::indent (createModelDotAndroid (indent,
- minimumSDKVersion,
- buildToolsVersion,
- bundleIdentifier), indent.length(), true)
- << newLine
- << CodeHelpers::indent (createModelDotCompileOptions (indent), indent.length(), true)
- << newLine
- << CodeHelpers::indent (createModelDotAndroidSources (indent), indent.length(), true)
- << newLine
- << CodeHelpers::indent (createModelDotAndroidNDK (indent), indent.length(), true)
- << newLine
- << CodeHelpers::indent (createModelDotAndroidDotBuildTypes (indent), indent.length(), true)
- << newLine
- << CodeHelpers::indent (createModelDotAndroidDotProductFlavors (indent), indent.length(), true)
- << "}";
-
- overwriteFileIfDifferentOrThrow (folder.getChildFile ("app/build.gradle"), memoryOutputStream);
- }
-
- static const char* getIndentationString() noexcept
- {
- return " ";
- }
-
- const File androidStudioExecutable;
-
- JUCE_DECLARE_NON_COPYABLE (AndroidStudioProjectExporter)
- };
|