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.

641 lines
24KB

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