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.

617 lines
23KB

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