The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

673 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class AndroidStudioProjectExporter : public AndroidProjectExporterBase
  18. {
  19. public:
  20. //==============================================================================
  21. static const char* getName() { return "Android Studio"; }
  22. static const char* getValueTreeTypeName() { return "ANDROIDSTUDIO"; }
  23. static AndroidStudioProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  24. {
  25. if (settings.hasType (getValueTreeTypeName()))
  26. return new AndroidStudioProjectExporter (project, settings);
  27. return nullptr;
  28. }
  29. //==============================================================================
  30. AndroidStudioProjectExporter (Project& p, const ValueTree& t)
  31. : AndroidProjectExporterBase (p, t),
  32. androidStudioExecutable (findAndroidStudioExecutable())
  33. {
  34. name = getName();
  35. if (getTargetLocationString().isEmpty())
  36. getTargetLocationValue() = getDefaultBuildsRootFolder() + "AndroidStudio";
  37. }
  38. //==============================================================================
  39. bool canLaunchProject() override
  40. {
  41. return androidStudioExecutable.exists();
  42. }
  43. bool launchProject() override
  44. {
  45. if (! androidStudioExecutable.exists())
  46. {
  47. jassertfalse;
  48. return false;
  49. }
  50. const File targetFolder (getTargetFolder());
  51. // we have to surround the path with extra quotes, otherwise Android Studio
  52. // will choke if there are any space characters in the path.
  53. return androidStudioExecutable.startAsProcess ("\"" + targetFolder.getFullPathName() + "\"");
  54. }
  55. void createExporterProperties (PropertyListBuilder& props) override
  56. {
  57. AndroidProjectExporterBase::createExporterProperties (props);
  58. props.add (new TextPropertyComponent (getNDKPlatformVersionValue(), "NDK Platform Version", 32, false),
  59. "The value to use for android$user.ndk.platformVersion in Gradle");
  60. props.add (new TextPropertyComponent (getBuildToolsVersionValue(), "Build Tools Version", 32, false),
  61. "The version of build tools use for build tools in Gradle");
  62. }
  63. Value getNDKPlatformVersionValue() { return getSetting (Ids::androidNdkPlatformVersion); }
  64. String getNDKPlatformVersionString() const { return settings [Ids::androidNdkPlatformVersion]; }
  65. Value getBuildToolsVersionValue() { return getSetting (Ids::buildToolsVersion); }
  66. String getBuildToolsVersionString() const { return settings [Ids::buildToolsVersion]; }
  67. void removeOldFiles (const File& targetFolder) const
  68. {
  69. targetFolder.getChildFile ("app/src").deleteRecursively();
  70. targetFolder.getChildFile ("app/build").deleteRecursively();
  71. targetFolder.getChildFile ("app/build.gradle").deleteFile();
  72. targetFolder.getChildFile ("gradle").deleteRecursively();
  73. targetFolder.getChildFile ("local.properties").deleteFile();
  74. targetFolder.getChildFile ("settings.gradle").deleteFile();
  75. }
  76. void create (const OwnedArray<LibraryModule>& modules) const override
  77. {
  78. const File targetFolder (getTargetFolder());
  79. removeOldFiles (targetFolder);
  80. {
  81. const String package (getActivityClassPackage());
  82. const String path (package.replaceCharacter ('.', File::separator));
  83. const File javaTarget (targetFolder.getChildFile ("app/src/main/java").getChildFile (path));
  84. copyActivityJavaFiles (modules, javaTarget, package);
  85. }
  86. writeSettingsDotGradle (targetFolder);
  87. writeLocalDotProperties (targetFolder);
  88. writeBuildDotGradleRoot (targetFolder);
  89. writeBuildDotGradleApp (targetFolder);
  90. writeGradleWrapperProperties (targetFolder);
  91. writeAndroidManifest (targetFolder);
  92. writeStringsXML (targetFolder);
  93. writeAppIcons (targetFolder);
  94. createSourceSymlinks (targetFolder);
  95. }
  96. static File findAndroidStudioExecutable()
  97. {
  98. #if JUCE_WINDOWS
  99. const File defaultInstallation ("C:\\Program Files\\Android\\Android Studio\\bin");
  100. if (defaultInstallation.exists())
  101. {
  102. {
  103. const File studio64 = defaultInstallation.getChildFile ("studio64.exe");
  104. if (studio64.existsAsFile())
  105. return studio64;
  106. }
  107. {
  108. const File studio = defaultInstallation.getChildFile ("studio.exe");
  109. if (studio.existsAsFile())
  110. return studio;
  111. }
  112. }
  113. #elif JUCE_MAC
  114. const File defaultInstallation ("/Applications/Android Studio.app");
  115. if (defaultInstallation.exists())
  116. return defaultInstallation;
  117. #endif
  118. return File::nonexistent;
  119. }
  120. protected:
  121. //==============================================================================
  122. class AndroidStudioBuildConfiguration : public BuildConfiguration
  123. {
  124. public:
  125. AndroidStudioBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
  126. : BuildConfiguration (p, settings, e)
  127. {
  128. if (getArchitectures().isEmpty())
  129. {
  130. if (isDebug())
  131. getArchitecturesValue() = "armeabi x86";
  132. else
  133. getArchitecturesValue() = "armeabi armeabi-v7a x86";
  134. }
  135. }
  136. Value getArchitecturesValue() { return getValue (Ids::androidArchitectures); }
  137. String getArchitectures() const { return config [Ids::androidArchitectures]; }
  138. var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); }
  139. void createConfigProperties (PropertyListBuilder& props) override
  140. {
  141. addGCCOptimisationProperty (props);
  142. props.add (new TextPropertyComponent (getArchitecturesValue(), "Architectures", 256, false),
  143. "A list of the ARM architectures to build (for a fat binary).");
  144. }
  145. };
  146. BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override
  147. {
  148. return new AndroidStudioBuildConfiguration (project, v, *this);
  149. }
  150. private:
  151. static void createSymboicLinkAndCreateParentFolders (const File& originalFile, const File& linkFile)
  152. {
  153. {
  154. const File linkFileParentDirectory (linkFile.getParentDirectory());
  155. // this will recursively creative the parent directories for the file
  156. // without this, the symlink would fail because it doesn't automatically create
  157. // the folders if they don't exist
  158. if (! linkFileParentDirectory.createDirectory())
  159. throw SaveError (String ("Could not create directory ") + linkFileParentDirectory.getFullPathName());
  160. }
  161. if (! originalFile.createSymbolicLink (linkFile, true))
  162. throw SaveError (String ("Failed to create symlink from ")
  163. + linkFile.getFullPathName() + " to "
  164. + originalFile.getFullPathName() + "!");
  165. }
  166. void makeSymlinksForGroup (const Project::Item& group, const File& targetFolder) const
  167. {
  168. if (! group.isGroup())
  169. {
  170. throw SaveError ("makeSymlinksForGroup was called with something other than a group!");
  171. }
  172. for (int i = 0; i < group.getNumChildren(); ++i)
  173. {
  174. const Project::Item& projectItem = group.getChild (i);
  175. if (projectItem.isGroup())
  176. {
  177. makeSymlinksForGroup (projectItem, targetFolder.getChildFile (projectItem.getName()));
  178. }
  179. else if (projectItem.shouldBeAddedToTargetProject()) // must be a file then
  180. {
  181. const File originalFile (projectItem.getFile());
  182. const File targetFile (targetFolder.getChildFile (originalFile.getFileName()));
  183. createSymboicLinkAndCreateParentFolders (originalFile, targetFile);
  184. }
  185. }
  186. }
  187. void createSourceSymlinks (const File& folder) const
  188. {
  189. const File targetFolder (folder.getChildFile ("app/src/main/jni"));
  190. // here we make symlinks to only to files included in the groups inside the project
  191. // this is because Android Studio does not have a concept of groups and just uses
  192. // the file system layout to determine what's to be compiled
  193. {
  194. const Array<Project::Item>& groups = getAllGroups();
  195. for (int i = 0; i < groups.size(); ++i)
  196. {
  197. const Project::Item projectItem (groups.getReference (i));
  198. const String projectItemName (projectItem.getName());
  199. if (projectItem.isGroup())
  200. makeSymlinksForGroup (projectItem, projectItemName == "Juce Modules" ? targetFolder.getChildFile ("JuceModules") : targetFolder);
  201. }
  202. }
  203. }
  204. void writeAppIcons (const File& folder) const
  205. {
  206. writeIcons (folder.getChildFile ("app/src/main/res/"));
  207. }
  208. void writeSettingsDotGradle (const File& folder) const
  209. {
  210. MemoryOutputStream memoryOutputStream;
  211. memoryOutputStream << "include ':app'";
  212. overwriteFileIfDifferentOrThrow (folder.getChildFile ("settings.gradle"), memoryOutputStream);
  213. }
  214. static String sanitisePath (String path)
  215. {
  216. return expandHomeFolderToken (path).replace ("\\", "\\\\");
  217. }
  218. static String expandHomeFolderToken (const String& path)
  219. {
  220. String homeFolder = File::getSpecialLocation (File::userHomeDirectory).getFullPathName();
  221. return path.replace ("${user.home}", homeFolder)
  222. .replace ("~", homeFolder);
  223. }
  224. void writeLocalDotProperties (const File& folder) const
  225. {
  226. MemoryOutputStream memoryOutputStream;
  227. memoryOutputStream << "ndk.dir=" << sanitisePath (getNDKPathString()) << newLine
  228. << "sdk.dir=" << sanitisePath (getSDKPathString());
  229. overwriteFileIfDifferentOrThrow (folder.getChildFile ("local.properties"), memoryOutputStream);
  230. }
  231. void writeGradleWrapperProperties (const File& folder) const
  232. {
  233. MemoryOutputStream memoryOutputStream;
  234. memoryOutputStream << "distributionUrl=https\\://services.gradle.org/distributions/gradle-2.10-all.zip";
  235. overwriteFileIfDifferentOrThrow (folder.getChildFile ("gradle/wrapper/gradle-wrapper.properties"), memoryOutputStream);
  236. }
  237. void writeBuildDotGradleRoot (const File& folder) const
  238. {
  239. MemoryOutputStream memoryOutputStream;
  240. const String indent = getIndentationString();
  241. // this is needed to make sure the correct version of the gradle build tools is available.
  242. // needs to be kept up to date!
  243. memoryOutputStream << "buildscript {" << newLine
  244. << indent << "repositories {" << newLine
  245. << indent << indent << "jcenter()" << newLine
  246. << indent << "}" << newLine
  247. << indent << "dependencies {" << newLine
  248. << indent << indent << "classpath 'com.android.tools.build:gradle-experimental:0.6.0-beta5'" << newLine
  249. << indent << "}" << newLine
  250. << "}" << newLine
  251. << newLine
  252. << "allprojects {" << newLine
  253. << indent << "repositories {" << newLine
  254. << indent << indent << "jcenter()" << newLine
  255. << indent << "}" << newLine
  256. << "}";
  257. overwriteFileIfDifferentOrThrow (folder.getChildFile ("build.gradle"), memoryOutputStream);
  258. }
  259. void writeStringsXML (const File& folder) const
  260. {
  261. XmlElement strings ("resources");
  262. XmlElement* resourceName = strings.createNewChildElement ("string");
  263. resourceName->setAttribute ("name", "app_name");
  264. resourceName->addTextElement (projectName);
  265. writeXmlOrThrow (strings, folder.getChildFile ("app/src/main/res/values/string.xml"), "utf-8", 100, true);
  266. }
  267. void writeAndroidManifest (const File& folder) const
  268. {
  269. ScopedPointer<XmlElement> manifest (createManifestXML());
  270. writeXmlOrThrow (*manifest, folder.getChildFile ("app/src/main/AndroidManifest.xml"), "utf-8", 100, true);
  271. }
  272. String createModelDotAndroid (const String& indent,
  273. const String& minimumSDKVersion,
  274. const String& buildToolsVersion,
  275. const String& bundleIdentifier) const
  276. {
  277. String result;
  278. result << "android {" << newLine
  279. << indent << "compileSdkVersion = " << minimumSDKVersion << newLine
  280. << indent << "buildToolsVersion = \"" << buildToolsVersion << "\"" << newLine
  281. << indent << "defaultConfig.with {" << newLine
  282. << indent << indent << "applicationId = \"" << bundleIdentifier.toLowerCase() << "\"" << newLine
  283. << indent << indent << "minSdkVersion.apiLevel = " << minimumSDKVersion << newLine
  284. << indent << indent << "targetSdkVersion.apiLevel = " << minimumSDKVersion << newLine
  285. << indent << "}" << newLine
  286. << "}" << newLine;
  287. return result;
  288. }
  289. String createModelDotAndroidSources (const String& indent) const
  290. {
  291. String result;
  292. result << "android.sources {" << newLine
  293. << indent << "main {" << newLine
  294. << indent << indent << "jni {" << newLine
  295. << indent << indent << indent << "source {" << newLine
  296. << indent << indent << indent << indent << "exclude \"**/JuceModules/\"" << newLine
  297. << indent << indent << indent << "}" << newLine
  298. << indent << indent << "}" << newLine
  299. << indent << "}" << newLine
  300. << "}" << newLine;
  301. return result;
  302. }
  303. struct ShouldBeAddedToProjectPredicate
  304. {
  305. bool operator() (const Project::Item& projectItem) const { return projectItem.shouldBeAddedToTargetProject(); }
  306. };
  307. StringArray getCPPFlags() const
  308. {
  309. StringArray result;
  310. result.add ("\"-fsigned-char\"");
  311. result.add ("\"-fexceptions\"");
  312. result.add ("\"-frtti\"");
  313. if (isCPP11Enabled())
  314. result.add ("\"-std=c++11\"");
  315. StringArray extraFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), " ", ""));
  316. for (int i = 0; i < extraFlags.size(); ++i)
  317. result.add (String ("\"") + extraFlags[i] + "\"");
  318. // include paths
  319. result.add ("\"-I${project.rootDir}/app\".toString()");
  320. result.add ("\"-I${ext.juceRootDir}\".toString()");
  321. result.add ("\"-I${ext.juceModuleDir}\".toString()");
  322. {
  323. Array<RelativePath> cppFiles;
  324. const Array<Project::Item>& groups = getAllGroups();
  325. for (int i = 0; i < groups.size(); ++i)
  326. findAllProjectItemsWithPredicate (groups.getReference (i), cppFiles, ShouldBeAddedToProjectPredicate());
  327. for (int i = 0; i < cppFiles.size(); ++i)
  328. {
  329. const RelativePath absoluteSourceFile (cppFiles.getReference (i).rebased (getTargetFolder(),
  330. project.getProjectFolder(),
  331. RelativePath::projectFolder));
  332. const String absoluteIncludeFolder (sanitisePath (project.getProjectFolder().getFullPathName() + "/"
  333. + absoluteSourceFile.toUnixStyle().upToLastOccurrenceOf ("/", false, false)));
  334. result.addIfNotAlreadyThere ("\"-I" + absoluteIncludeFolder + "\".toString()");
  335. }
  336. }
  337. return result;
  338. }
  339. StringArray getLDLibs() const
  340. {
  341. StringArray result;
  342. result.add ("android");
  343. result.add ("EGL");
  344. result.add ("GLESv2");
  345. result.add ("log");
  346. result.addArray (StringArray::fromTokens(getExternalLibrariesString(), ";", ""));
  347. return result;
  348. }
  349. String createModelDotAndroidNDK (const String& indent) const
  350. {
  351. String result;
  352. const String platformVersion (getNDKPlatformVersionString());
  353. result << "android.ndk {" << newLine
  354. << indent << "moduleName = \"juce_jni\"" << newLine
  355. << indent << "stl = \"c++_static\"" << newLine
  356. << indent << "toolchain = \"clang\"" << newLine
  357. << indent << "toolchainVersion = 3.6" << newLine;
  358. if (platformVersion.isNotEmpty())
  359. result << indent << "platformVersion = " << getNDKPlatformVersionString() << newLine;
  360. result << indent << "ext {" << newLine
  361. << indent << indent << "juceRootDir = \"" << "${project.rootDir}/../../../../" << "\".toString()" << newLine
  362. << indent << indent << "juceModuleDir = \"" << "${juceRootDir}/modules" << "\".toString()" << newLine
  363. << indent << "}" << newLine;
  364. // CPP flags
  365. {
  366. StringArray cppFlags (getCPPFlags());
  367. for (int i = 0; i < cppFlags.size(); ++i)
  368. result << indent << "cppFlags.add(" << cppFlags[i] << ")" << newLine;
  369. }
  370. // libraries
  371. {
  372. StringArray libraries (getLDLibs());
  373. result << indent << "ldLibs.addAll(";
  374. for (int i = 0; i < libraries.size(); ++i)
  375. {
  376. result << "\"" << libraries[i] << "\"";
  377. if (i + 1 != libraries.size())
  378. result << ", ";
  379. }
  380. result << ")" << newLine;
  381. }
  382. result << "}" << newLine;
  383. return result;
  384. }
  385. String getModelDotAndroidDotBuildTypesFlags (const String& indent, const ConstConfigIterator& config) const
  386. {
  387. const String configName (config->getName());
  388. // there appears to be an issue with build types that have a name other than
  389. // "debug" or "release". Apparently this is hard coded in Android Studio ...
  390. if (configName != "Debug" && configName != "Release")
  391. throw SaveError ("Build configurations other than Debug and Release are not yet support for Android Studio");
  392. StringArray rootFlags; // model.android.buildTypes.debug/release { ... }
  393. StringArray ndkFlags; // model.android.buildTypes.debug/release.ndk.with { ... }
  394. if (config->isDebug())
  395. {
  396. ndkFlags.add ("debuggable = true");
  397. ndkFlags.add ("cppFlags.add(\"-g\")");
  398. ndkFlags.add ("cppFlags.add(\"-DDEBUG=1\")");
  399. ndkFlags.add ("cppFlags.add(\"-D_DEBUG=1\")");
  400. }
  401. else
  402. {
  403. rootFlags.add ("signingConfig = $(\"android.signingConfigs.releaseConfig\")");
  404. ndkFlags.add ("cppFlags.add(\"-DNDEBUG=1\")");
  405. }
  406. const StringArray& headerSearchPaths = config->getHeaderSearchPaths();
  407. for (int i = 0; i < headerSearchPaths.size(); ++i)
  408. ndkFlags.add ("cppFlags.add(\"-I" + sanitisePath (headerSearchPaths[i]) + "\".toString())");
  409. const StringArray& librarySearchPaths = config->getLibrarySearchPaths();
  410. for (int i = 0; i < librarySearchPaths.size(); ++i)
  411. ndkFlags.add ("cppFlags.add(\"-L" + sanitisePath (librarySearchPaths[i]) + "\".toString())");
  412. {
  413. StringPairArray preprocessorDefinitions = config->getAllPreprocessorDefs();
  414. preprocessorDefinitions.set ("JUCE_ANDROID", "1");
  415. preprocessorDefinitions.set ("JUCE_ANDROID_API_VERSION", getMinimumSDKVersionString());
  416. preprocessorDefinitions.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_'));
  417. preprocessorDefinitions.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\\\"" + getActivityClassPath().replaceCharacter('.', '/') + "\\\"");
  418. const StringArray& keys = preprocessorDefinitions.getAllKeys();
  419. for (int i = 0; i < keys.size(); ++i)
  420. ndkFlags.add (String ("cppFlags.add(\"-D") + keys[i] + String ("=") + preprocessorDefinitions[keys[i]] + "\")");
  421. }
  422. ndkFlags.add ("cppFlags.add(\"-O" + config->getGCCOptimisationFlag() + "\")");
  423. String result;
  424. result << configName.toLowerCase() << " {" << newLine;
  425. for (int i = 0; i < rootFlags.size(); ++i)
  426. result << indent << rootFlags[i] << newLine;
  427. result << indent << "ndk.with {" << newLine;
  428. for (int i = 0; i < ndkFlags.size(); ++i)
  429. result << indent << indent << ndkFlags[i] << newLine;
  430. result << indent << "}" << newLine
  431. << "}" << newLine;
  432. return result;
  433. }
  434. String createModelDotAndroidDotBuildTypes (const String& indent) const
  435. {
  436. String result;
  437. result << "android.buildTypes {" << newLine;
  438. for (ConstConfigIterator config (*this); config.next();)
  439. result << CodeHelpers::indent (getModelDotAndroidDotBuildTypesFlags (indent, config), indent.length(), true);
  440. result << "}" << newLine;
  441. return result;
  442. }
  443. String createModelDotAndroidDotSigningConfigs (const String& indent) const
  444. {
  445. String result;
  446. result << "android.signingConfigs {" << newLine
  447. << indent << "create(\"releaseConfig\") {" << newLine
  448. << indent << indent << "storeFile = new File(\"" << sanitisePath (getKeyStoreString()) << "\")" << newLine
  449. << indent << indent << "storePassword = \"" << getKeyStorePassString() << "\"" << newLine
  450. << indent << indent << "keyAlias = \"" << getKeyAliasString() << "\"" << newLine
  451. << indent << indent << "keyPassword = \"" << getKeyAliasPassString() << "\"" << newLine
  452. << indent << indent << "storeType = \"jks\"" << newLine
  453. << indent << "}" << newLine
  454. << "}" << newLine;
  455. return result;
  456. }
  457. String createModelDotAndroidDotProductFlavors (const String& indent) const
  458. {
  459. String result;
  460. result << "android.productFlavors {" << newLine;
  461. // TODO! - this needs to be changed so that it generates seperate flags for debug and release ...
  462. // at present, it just generates all ABIs for all build types
  463. StringArray architectures (StringArray::fromTokens (getABIs<AndroidStudioBuildConfiguration> (true), " ", ""));
  464. architectures.mergeArray (StringArray::fromTokens (getABIs<AndroidStudioBuildConfiguration> (false), " ", ""));
  465. if (architectures.size() == 0)
  466. throw SaveError ("Can't build for no architectures!");
  467. for (int i = 0; i < architectures.size(); ++i)
  468. {
  469. String architecture (architectures[i].trim());
  470. if (architecture.isEmpty())
  471. continue;
  472. result << indent << "create(\"" << architecture << "\") {" << newLine
  473. << indent << indent << "ndk.abiFilters.add(\"" << architecture << "\")" << newLine
  474. << indent << "}" << newLine;
  475. }
  476. result << "}" << newLine;
  477. return result;
  478. }
  479. void writeBuildDotGradleApp (const File& folder) const
  480. {
  481. MemoryOutputStream memoryOutputStream;
  482. const String indent = getIndentationString();
  483. const String minimumSDKVersion = getMinimumSDKVersionString();
  484. const String bundleIdentifier = project.getBundleIdentifier().toString();
  485. String buildToolsVersion = getBuildToolsVersionString();
  486. if (buildToolsVersion.isEmpty())
  487. buildToolsVersion = "23.0.1";
  488. memoryOutputStream << "apply plugin: 'com.android.model.application'" << newLine
  489. << newLine
  490. << "model {" << newLine
  491. << CodeHelpers::indent (createModelDotAndroid (indent,
  492. minimumSDKVersion,
  493. buildToolsVersion,
  494. bundleIdentifier), indent.length(), true)
  495. << newLine
  496. << CodeHelpers::indent (createModelDotAndroidNDK (indent), indent.length(), true)
  497. << newLine
  498. << CodeHelpers::indent (createModelDotAndroidSources (indent), indent.length(), true)
  499. << newLine
  500. << CodeHelpers::indent (createModelDotAndroidDotBuildTypes (indent), indent.length(), true)
  501. << newLine
  502. << CodeHelpers::indent (createModelDotAndroidDotSigningConfigs (indent), indent.length(), true)
  503. << newLine
  504. << CodeHelpers::indent (createModelDotAndroidDotProductFlavors (indent), indent.length(), true)
  505. << "}";
  506. overwriteFileIfDifferentOrThrow (folder.getChildFile ("app/build.gradle"), memoryOutputStream);
  507. }
  508. static const char* getIndentationString() noexcept
  509. {
  510. return " ";
  511. }
  512. const File androidStudioExecutable;
  513. JUCE_DECLARE_NON_COPYABLE (AndroidStudioProjectExporter)
  514. };