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.

1464 lines
65KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. class AndroidProjectExporter : public ProjectExporter
  20. {
  21. public:
  22. //==============================================================================
  23. bool isXcode() const override { return false; }
  24. bool isVisualStudio() const override { return false; }
  25. bool isCodeBlocks() const override { return false; }
  26. bool isMakefile() const override { return false; }
  27. bool isAndroidStudio() const override { return true; }
  28. bool isAndroid() const override { return true; }
  29. bool isWindows() const override { return false; }
  30. bool isLinux() const override { return false; }
  31. bool isOSX() const override { return false; }
  32. bool isiOS() const override { return false; }
  33. bool usesMMFiles() const override { return false; }
  34. bool canCopeWithDuplicateFiles() override { return false; }
  35. bool supportsUserDefinedConfigurations() const override { return true; }
  36. bool supportsTargetType (ProjectType::Target::Type type) const override
  37. {
  38. switch (type)
  39. {
  40. case ProjectType::Target::GUIApp:
  41. case ProjectType::Target::StaticLibrary:
  42. case ProjectType::Target::StandalonePlugIn:
  43. return true;
  44. default:
  45. break;
  46. }
  47. return false;
  48. }
  49. //==============================================================================
  50. void addPlatformSpecificSettingsForProjectType (const ProjectType&) override
  51. {
  52. // no-op.
  53. }
  54. //==============================================================================
  55. void createExporterProperties (PropertyListBuilder& props) override
  56. {
  57. createBaseExporterProperties (props);
  58. createToolchainExporterProperties (props);
  59. createManifestExporterProperties (props);
  60. createLibraryModuleExporterProperties (props);
  61. createCodeSigningExporterProperties (props);
  62. createOtherExporterProperties (props);
  63. }
  64. static const char* getName() { return "Android"; }
  65. static const char* getValueTreeTypeName() { return "ANDROIDSTUDIO"; }
  66. static AndroidProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  67. {
  68. if (settings.hasType (getValueTreeTypeName()))
  69. return new AndroidProjectExporter (project, settings);
  70. return nullptr;
  71. }
  72. //==============================================================================
  73. CachedValue<String> androidRepositories, androidDependencies,
  74. androidScreenOrientation, androidActivityClass, androidActivitySubClassName,
  75. androidManifestCustomXmlElements, androidVersionCode, androidMinimumSDK, androidTheme,
  76. androidSharedLibraries, androidStaticLibraries, androidExtraAssetsFolder;
  77. CachedValue<bool> androidInternetNeeded, androidMicNeeded, androidBluetoothNeeded,
  78. androidExternalReadPermission, androidExternalWritePermission;
  79. CachedValue<String> androidOtherPermissions;
  80. CachedValue<String> androidKeyStore, androidKeyStorePass, androidKeyAlias, androidKeyAliasPass;
  81. CachedValue<String> gradleVersion, androidPluginVersion, gradleToolchain, buildToolsVersion;
  82. //==============================================================================
  83. AndroidProjectExporter (Project& p, const ValueTree& t)
  84. : ProjectExporter (p, t),
  85. androidRepositories (settings, Ids::androidRepositories, nullptr, ""),
  86. androidDependencies (settings, Ids::androidDependencies, nullptr, ""),
  87. androidScreenOrientation (settings, Ids::androidScreenOrientation, nullptr, "unspecified"),
  88. androidActivityClass (settings, Ids::androidActivityClass, nullptr, createDefaultClassName()),
  89. androidActivitySubClassName (settings, Ids::androidActivitySubClassName, nullptr),
  90. androidManifestCustomXmlElements (settings, Ids::androidManifestCustomXmlElements, nullptr, ""),
  91. androidVersionCode (settings, Ids::androidVersionCode, nullptr, "1"),
  92. androidMinimumSDK (settings, Ids::androidMinimumSDK, nullptr, "10"),
  93. androidTheme (settings, Ids::androidTheme, nullptr),
  94. androidSharedLibraries (settings, Ids::androidSharedLibraries, nullptr, ""),
  95. androidStaticLibraries (settings, Ids::androidStaticLibraries, nullptr, ""),
  96. androidExtraAssetsFolder (settings, Ids::androidExtraAssetsFolder, nullptr, ""),
  97. androidInternetNeeded (settings, Ids::androidInternetNeeded, nullptr, true),
  98. androidMicNeeded (settings, Ids::microphonePermissionNeeded, nullptr, false),
  99. androidBluetoothNeeded (settings, Ids::androidBluetoothNeeded, nullptr, true),
  100. androidExternalReadPermission (settings, Ids::androidExternalReadNeeded, nullptr, true),
  101. androidExternalWritePermission (settings, Ids::androidExternalWriteNeeded, nullptr, true),
  102. androidOtherPermissions (settings, Ids::androidOtherPermissions, nullptr),
  103. androidKeyStore (settings, Ids::androidKeyStore, nullptr, "${user.home}/.android/debug.keystore"),
  104. androidKeyStorePass (settings, Ids::androidKeyStorePass, nullptr, "android"),
  105. androidKeyAlias (settings, Ids::androidKeyAlias, nullptr, "androiddebugkey"),
  106. androidKeyAliasPass (settings, Ids::androidKeyAliasPass, nullptr, "android"),
  107. gradleVersion (settings, Ids::gradleVersion, nullptr, "3.3"),
  108. androidPluginVersion (settings, Ids::androidPluginVersion, nullptr, "2.3.1"),
  109. gradleToolchain (settings, Ids::gradleToolchain, nullptr, "clang"),
  110. buildToolsVersion (settings, Ids::buildToolsVersion, nullptr, "25.0.2"),
  111. AndroidExecutable (findAndroidExecutable())
  112. {
  113. initialiseDependencyPathValues();
  114. name = getName();
  115. if (getTargetLocationString().isEmpty())
  116. getTargetLocationValue() = getDefaultBuildsRootFolder() + "Android";
  117. }
  118. //==============================================================================
  119. void createToolchainExporterProperties (PropertyListBuilder& props)
  120. {
  121. props.add (new TextWithDefaultPropertyComponent<String> (gradleVersion, "gradle version", 32),
  122. "The version of gradle that is used to build this app (3.3 is fine for JUCE)");
  123. props.add (new TextWithDefaultPropertyComponent<String> (androidPluginVersion, "android plug-in version", 32),
  124. "The version of the android build plugin for gradle that is used to build this app");
  125. static const char* toolchains[] = { "clang", "gcc", nullptr };
  126. props.add (new ChoicePropertyComponent (gradleToolchain.getPropertyAsValue(), "NDK Toolchain", StringArray (toolchains), Array<var> (toolchains)),
  127. "The toolchain that gradle should invoke for NDK compilation (variable model.android.ndk.tooclhain in app/build.gradle)");
  128. props.add (new TextWithDefaultPropertyComponent<String> (buildToolsVersion, "Android build tools version", 32),
  129. "The Android build tools version that should use to build this app");
  130. }
  131. void createLibraryModuleExporterProperties (PropertyListBuilder& props)
  132. {
  133. props.add (new TextPropertyComponent (androidStaticLibraries.getPropertyAsValue(), "Import static library modules", 8192, true),
  134. "Comma or whitespace delimited list of static libraries (.a) defined in NDK_MODULE_PATH.");
  135. props.add (new TextPropertyComponent (androidSharedLibraries.getPropertyAsValue(), "Import shared library modules", 8192, true),
  136. "Comma or whitespace delimited list of shared libraries (.so) defined in NDK_MODULE_PATH.");
  137. }
  138. //==============================================================================
  139. bool canLaunchProject() override
  140. {
  141. return AndroidExecutable.exists();
  142. }
  143. bool launchProject() override
  144. {
  145. if (! AndroidExecutable.exists())
  146. {
  147. jassertfalse;
  148. return false;
  149. }
  150. const File targetFolder (getTargetFolder());
  151. // we have to surround the path with extra quotes, otherwise Android Studio
  152. // will choke if there are any space characters in the path.
  153. return AndroidExecutable.startAsProcess ("\"" + targetFolder.getFullPathName() + "\"");
  154. }
  155. //==============================================================================
  156. void create (const OwnedArray<LibraryModule>& modules) const override
  157. {
  158. const File targetFolder (getTargetFolder());
  159. const File appFolder (targetFolder.getChildFile (isLibrary() ? "lib" : "app"));
  160. removeOldFiles (targetFolder);
  161. {
  162. const String package (getActivityClassPackage());
  163. const String path (package.replaceCharacter ('.', File::separator));
  164. const File javaTarget (targetFolder.getChildFile ("app/src/main/java").getChildFile (path));
  165. if (! isLibrary())
  166. copyActivityJavaFiles (modules, javaTarget, package);
  167. }
  168. copyExtraResourceFiles();
  169. writeFile (targetFolder, "settings.gradle", isLibrary() ? "include ':lib'" : "include ':app'");
  170. writeFile (targetFolder, "build.gradle", getProjectBuildGradleFileContent());
  171. writeFile (appFolder, "build.gradle", getAppBuildGradleFileContent());
  172. writeFile (targetFolder, "local.properties", getLocalPropertiesFileContent());
  173. writeFile (targetFolder, "gradle/wrapper/gradle-wrapper.properties", getGradleWrapperPropertiesFileContent());
  174. writeBinaryFile (targetFolder, "gradle/wrapper/LICENSE-for-gradlewrapper.txt", BinaryData::LICENSE, BinaryData::LICENSESize);
  175. writeBinaryFile (targetFolder, "gradle/wrapper/gradle-wrapper.jar", BinaryData::gradlewrapper_jar, BinaryData::gradlewrapper_jarSize);
  176. writeBinaryFile (targetFolder, "gradlew", BinaryData::gradlew, BinaryData::gradlewSize);
  177. writeBinaryFile (targetFolder, "gradlew.bat", BinaryData::gradlew_bat, BinaryData::gradlew_batSize);
  178. targetFolder.getChildFile ("gradlew").setExecutePermission (true);
  179. writeAndroidManifest (appFolder);
  180. if (! isLibrary())
  181. {
  182. writeStringsXML (targetFolder);
  183. writeAppIcons (targetFolder);
  184. }
  185. writeCmakeFile (appFolder.getChildFile ("CMakeLists.txt"));
  186. const String androidExtraAssetsFolderValue = androidExtraAssetsFolder.get();
  187. if (androidExtraAssetsFolderValue.isNotEmpty())
  188. {
  189. File extraAssets (getProject().getFile().getParentDirectory().getChildFile(androidExtraAssetsFolderValue));
  190. if (extraAssets.exists() && extraAssets.isDirectory())
  191. {
  192. const File assetsFolder (appFolder.getChildFile ("src/main/assets"));
  193. if (assetsFolder.deleteRecursively())
  194. extraAssets.copyDirectoryTo (assetsFolder);
  195. }
  196. }
  197. }
  198. void removeOldFiles (const File& targetFolder) const
  199. {
  200. targetFolder.getChildFile ("app/src").deleteRecursively();
  201. targetFolder.getChildFile ("app/build").deleteRecursively();
  202. targetFolder.getChildFile ("app/build.gradle").deleteFile();
  203. targetFolder.getChildFile ("gradle").deleteRecursively();
  204. targetFolder.getChildFile ("local.properties").deleteFile();
  205. targetFolder.getChildFile ("settings.gradle").deleteFile();
  206. }
  207. void writeFile (const File& gradleProjectFolder, const String& filePath, const String& fileContent) const
  208. {
  209. MemoryOutputStream outStream;
  210. outStream << fileContent;
  211. overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream);
  212. }
  213. void writeBinaryFile (const File& gradleProjectFolder, const String& filePath, const char* binaryData, const int binarySize) const
  214. {
  215. MemoryOutputStream outStream;
  216. outStream.write (binaryData, static_cast<size_t> (binarySize));
  217. overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream);
  218. }
  219. //==============================================================================
  220. static File findAndroidExecutable()
  221. {
  222. #if JUCE_WINDOWS
  223. const File defaultInstallation ("C:\\Program Files\\Android\\Android Studio\\bin");
  224. if (defaultInstallation.exists())
  225. {
  226. {
  227. const File studio64 = defaultInstallation.getChildFile ("studio64.exe");
  228. if (studio64.existsAsFile())
  229. return studio64;
  230. }
  231. {
  232. const File studio = defaultInstallation.getChildFile ("studio.exe");
  233. if (studio.existsAsFile())
  234. return studio;
  235. }
  236. }
  237. #elif JUCE_MAC
  238. const File defaultInstallation ("/Applications/Android Studio.app");
  239. if (defaultInstallation.exists())
  240. return defaultInstallation;
  241. #endif
  242. return {};
  243. }
  244. protected:
  245. //==============================================================================
  246. class AndroidBuildConfiguration : public BuildConfiguration
  247. {
  248. public:
  249. AndroidBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
  250. : BuildConfiguration (p, settings, e)
  251. {
  252. if (getArchitectures().isEmpty())
  253. {
  254. if (isDebug())
  255. getArchitecturesValue() = "armeabi x86";
  256. else
  257. getArchitecturesValue() = "";
  258. }
  259. }
  260. Value getArchitecturesValue() { return getValue (Ids::androidArchitectures); }
  261. String getArchitectures() const { return config [Ids::androidArchitectures]; }
  262. Value getAdditionalXmlResourcesValue() { return getValue (Ids::androidAdditionalXmlValueResources); }
  263. String getAdditionalXmlResources() const { return config [Ids::androidAdditionalXmlValueResources]; }
  264. Value getCustomStringsXmlValue() { return getValue (Ids::androidCustomStringXmlElements); }
  265. String getCustomStringsXml() const { return config [Ids::androidCustomStringXmlElements]; }
  266. var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); }
  267. void createConfigProperties (PropertyListBuilder& props) override
  268. {
  269. addGCCOptimisationProperty (props);
  270. props.add (new TextPropertyComponent (getArchitecturesValue(), "Architectures", 256, false),
  271. "A list of the ARM architectures to build (for a fat binary). Leave empty to build for all possible android archiftectures.");
  272. props.add (new TextPropertyComponent (getAdditionalXmlResourcesValue(), "Extra Android XML Value Resources", 2048, true),
  273. "Paths to additional \"value resource\" files in XML format that should be included in the app (one per line). "
  274. "If you have additional XML resources that should be treated as value resources, add them here.");
  275. props.add (new TextPropertyComponent (getCustomStringsXmlValue(), "Custom string.xml elements", 8192, true),
  276. "You can specify custom XML elements that will be added to string.xml as children of <resources> element.");
  277. }
  278. String getProductFlavourNameIdentifier() const
  279. {
  280. return getName().toLowerCase().replaceCharacter (L' ', L'_') + String ("_");
  281. }
  282. String getProductFlavourCMakeIdentifier() const
  283. {
  284. return getName().toUpperCase().replaceCharacter (L' ', L'_');
  285. }
  286. };
  287. BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override
  288. {
  289. return new AndroidBuildConfiguration (project, v, *this);
  290. }
  291. private:
  292. void writeCmakeFile (const File& file) const
  293. {
  294. MemoryOutputStream mo;
  295. mo << "# Automatically generated makefile, created by the Projucer" << newLine
  296. << "# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!" << newLine
  297. << newLine;
  298. mo << "cmake_minimum_required(VERSION 3.4.1)" << newLine << newLine;
  299. if (! isLibrary())
  300. mo << "SET(BINARY_NAME \"juce_jni\")" << newLine << newLine;
  301. mo << "add_library(\"cpufeatures\" STATIC \"${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c\")" << newLine << newLine;
  302. {
  303. StringArray projectDefines (getEscapedPreprocessorDefs (getProjectPreprocessorDefs()));
  304. if (projectDefines.size() > 0)
  305. mo << "add_definitions(" << projectDefines.joinIntoString (" ") << ")" << newLine << newLine;
  306. }
  307. {
  308. mo << "include_directories( AFTER" << newLine;
  309. for (auto& path : extraSearchPaths)
  310. mo << " \"" << escapeDirectoryForCmake (path) << "\"" << newLine;
  311. mo << " \"${ANDROID_NDK}/sources/android/cpufeatures\"" << newLine;
  312. mo << ")" << newLine << newLine;
  313. }
  314. const String& cfgExtraLinkerFlags = getExtraLinkerFlagsString();
  315. if (cfgExtraLinkerFlags.isNotEmpty())
  316. {
  317. mo << "SET( JUCE_LDFLAGS \"" << cfgExtraLinkerFlags.replace ("\"", "\\\"") << "\")" << newLine;
  318. mo << "SET( CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} ${JUCE_LDFLAGS}\")" << newLine << newLine;
  319. }
  320. const StringArray userLibraries = StringArray::fromTokens(getExternalLibrariesString(), ";", "");
  321. if (getNumConfigurations() > 0)
  322. {
  323. bool first = true;
  324. for (ConstConfigIterator config (*this); config.next();)
  325. {
  326. const auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  327. const StringArray& libSearchPaths = cfg.getLibrarySearchPaths();
  328. const StringPairArray& cfgDefines = getConfigPreprocessorDefs (cfg);
  329. const StringArray& cfgHeaderPaths = cfg.getHeaderSearchPaths();
  330. const StringArray& cfgLibraryPaths = cfg.getLibrarySearchPaths();
  331. if (! isLibrary() && libSearchPaths.size() == 0 && cfgDefines.size() == 0
  332. && cfgHeaderPaths.size() == 0 && cfgLibraryPaths.size() == 0)
  333. continue;
  334. mo << (first ? "IF" : "ELSEIF") << "(JUCE_BUILD_CONFIGFURATION MATCHES \"" << cfg.getProductFlavourCMakeIdentifier() <<"\")" << newLine;
  335. if (isLibrary())
  336. mo << " SET(BINARY_NAME \"" << getNativeModuleBinaryName (cfg) << "\")" << newLine;
  337. writeCmakePathLines (mo, " ", "link_directories(", libSearchPaths);
  338. if (cfgDefines.size() > 0)
  339. mo << " add_definitions(" << getEscapedPreprocessorDefs (cfgDefines).joinIntoString (" ") << ")" << newLine;
  340. writeCmakePathLines (mo, " ", "include_directories( AFTER", cfgHeaderPaths);
  341. if (userLibraries.size() > 0)
  342. {
  343. for (auto& lib : userLibraries)
  344. {
  345. String findLibraryCmd;
  346. findLibraryCmd << "find_library(" << lib.toLowerCase().replaceCharacter (L' ', L'_')
  347. << " \"" << lib << "\" PATHS";
  348. writeCmakePathLines (mo, " ", findLibraryCmd, cfgLibraryPaths, " NO_CMAKE_FIND_ROOT_PATH)");
  349. }
  350. mo << newLine;
  351. }
  352. first = false;
  353. }
  354. if (! first)
  355. {
  356. ProjectExporter::BuildConfiguration::Ptr config (getConfiguration(0));
  357. if (config)
  358. {
  359. if (const auto* cfg = dynamic_cast<const AndroidBuildConfiguration*> (config.get()))
  360. {
  361. mo << "ELSE(JUCE_BUILD_CONFIGFURATION MATCHES \"" << cfg->getProductFlavourCMakeIdentifier() <<"\")" << newLine;
  362. mo << " MESSAGE( FATAL_ERROR \"No matching build-configuration found.\" )" << newLine;
  363. mo << "ENDIF(JUCE_BUILD_CONFIGFURATION MATCHES \"" << cfg->getProductFlavourCMakeIdentifier() <<"\")" << newLine << newLine;
  364. }
  365. }
  366. }
  367. }
  368. Array<RelativePath> excludeFromBuild;
  369. mo << "add_library( ${BINARY_NAME}" << newLine;
  370. mo << newLine;
  371. mo << " " << (getProject().getProjectType().isStaticLibrary() ? "STATIC" : "SHARED") << newLine;
  372. mo << newLine;
  373. addCompileUnits (mo, excludeFromBuild);
  374. mo << ")" << newLine << newLine;
  375. if (excludeFromBuild.size() > 0)
  376. {
  377. for (auto& exclude : excludeFromBuild)
  378. mo << "set_source_files_properties(\"" << exclude.toUnixStyle() << "\" PROPERTIES HEADER_FILE_ONLY TRUE)" << newLine;
  379. mo << newLine;
  380. }
  381. StringArray libraries (getAndroidLibraries());
  382. if (libraries.size() > 0)
  383. {
  384. for (auto& lib : libraries)
  385. mo << "find_library(" << lib.toLowerCase().replaceCharacter (L' ', L'_') << " \"" << lib << "\")" << newLine;
  386. mo << newLine;
  387. }
  388. libraries.addArray (userLibraries);
  389. mo << "target_link_libraries( ${BINARY_NAME}";
  390. if (libraries.size() > 0)
  391. {
  392. mo << newLine << newLine;
  393. for (auto& lib : libraries)
  394. mo << " ${" << lib.toLowerCase().replaceCharacter (L' ', L'_') << "}" << newLine;
  395. mo << " \"cpufeatures\"" << newLine;
  396. }
  397. mo << ")" << newLine;
  398. overwriteFileIfDifferentOrThrow (file, mo);
  399. }
  400. //==============================================================================
  401. String getProjectBuildGradleFileContent() const
  402. {
  403. MemoryOutputStream mo;
  404. mo << "buildscript {" << newLine;
  405. mo << " repositories {" << newLine;
  406. mo << " jcenter()" << newLine;
  407. mo << " }" << newLine;
  408. mo << " dependencies {" << newLine;
  409. mo << " classpath 'com.android.tools.build:gradle:" << androidPluginVersion.get() << "'" << newLine;
  410. mo << " }" << newLine;
  411. mo << "}" << newLine;
  412. mo << "" << newLine;
  413. mo << "allprojects {" << newLine;
  414. mo << " repositories {" << newLine;
  415. mo << " jcenter()" << newLine;
  416. mo << " }" << newLine;
  417. mo << "}" << newLine;
  418. return mo.toString();
  419. }
  420. //==============================================================================
  421. String getAppBuildGradleFileContent() const
  422. {
  423. MemoryOutputStream mo;
  424. mo << "apply plugin: 'com.android." << (isLibrary() ? "library" : "application") << "'" << newLine << newLine;
  425. mo << "android {" << newLine;
  426. mo << " compileSdkVersion " << androidMinimumSDK.get().getIntValue() << newLine;
  427. mo << " buildToolsVersion \"" << buildToolsVersion.get() << "\"" << newLine;
  428. mo << " externalNativeBuild {" << newLine;
  429. mo << " cmake {" << newLine;
  430. mo << " path \"CMakeLists.txt\"" << newLine;
  431. mo << " }" << newLine;
  432. mo << " }" << newLine;
  433. mo << getAndroidSigningConfig() << newLine;
  434. mo << getAndroidDefaultConfig() << newLine;
  435. mo << getAndroidBuildTypes() << newLine;
  436. mo << getAndroidProductFlavours() << newLine;
  437. mo << getAndroidVariantFilter() << newLine;
  438. mo << getAndroidRepositories() << newLine;
  439. mo << getAndroidDependencies() << newLine;
  440. mo << "}" << newLine << newLine;
  441. return mo.toString();
  442. }
  443. String getAndroidProductFlavours() const
  444. {
  445. MemoryOutputStream mo;
  446. mo << " productFlavors {" << newLine;
  447. for (ConstConfigIterator config (*this); config.next();)
  448. {
  449. const auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  450. mo << " " << cfg.getProductFlavourNameIdentifier() << " {" << newLine;
  451. if (cfg.getArchitectures().isNotEmpty())
  452. {
  453. mo << " ndk {" << newLine;
  454. mo << " abiFilters " << toGradleList (StringArray::fromTokens (cfg.getArchitectures(), " ", "")) << newLine;
  455. mo << " }" << newLine;
  456. }
  457. mo << " externalNativeBuild {" << newLine;
  458. mo << " cmake {" << newLine;
  459. if (getProject().getProjectType().isStaticLibrary())
  460. mo << " targets \"" << getNativeModuleBinaryName (cfg) << "\"" << newLine;
  461. mo << " arguments \"-DJUCE_BUILD_CONFIGFURATION=" << cfg.getProductFlavourCMakeIdentifier() << "\"" << newLine;
  462. mo << " cFlags \"-O" << cfg.getGCCOptimisationFlag() << "\"" << newLine;
  463. mo << " cppFlags \"-O" << cfg.getGCCOptimisationFlag() << "\"" << newLine;
  464. mo << " }" << newLine;
  465. mo << " }" << newLine;
  466. mo << " }" << newLine;
  467. }
  468. mo << " }" << newLine;
  469. return mo.toString();
  470. }
  471. String getAndroidSigningConfig() const
  472. {
  473. MemoryOutputStream mo;
  474. String keyStoreFilePath = androidKeyStore.get().replace ("${user.home}", "${System.properties['user.home']}")
  475. .replace ("/", "${File.separator}");
  476. mo << " signingConfigs {" << newLine;
  477. mo << " release {" << newLine;
  478. mo << " storeFile file(\"" << keyStoreFilePath << "\")" << newLine;
  479. mo << " storePassword \"" << androidKeyStorePass.get() << "\"" << newLine;
  480. mo << " keyAlias \"" << androidKeyAlias.get() << "\"" << newLine;
  481. mo << " keyPassword \"" << androidKeyAliasPass.get() << "\"" << newLine;
  482. mo << " storeType \"jks\"" << newLine;
  483. mo << " }" << newLine;
  484. mo << " }" << newLine;
  485. return mo.toString();
  486. }
  487. String getAndroidDefaultConfig() const
  488. {
  489. const String bundleIdentifier = project.getBundleIdentifier().toString().toLowerCase();
  490. const StringArray& cmakeDefs = getCmakeDefinitions();
  491. const StringArray& cFlags = getProjectCompilerFlags();
  492. const StringArray& cxxFlags = getProjectCxxCompilerFlags();
  493. const int minSdkVersion = androidMinimumSDK.get().getIntValue();
  494. MemoryOutputStream mo;
  495. mo << " defaultConfig {" << newLine;
  496. if (! isLibrary())
  497. mo << " applicationId \"" << bundleIdentifier << "\"" << newLine;
  498. mo << " minSdkVersion " << minSdkVersion << newLine;
  499. mo << " targetSdkVersion " << minSdkVersion << newLine;
  500. mo << " externalNativeBuild {" << newLine;
  501. mo << " cmake {" << newLine;
  502. mo << " arguments " << cmakeDefs.joinIntoString (", ") << newLine;
  503. if (cFlags.size() > 0)
  504. mo << " cFlags " << cFlags.joinIntoString (", ") << newLine;
  505. if (cxxFlags.size() > 0)
  506. mo << " cppFlags " << cxxFlags.joinIntoString (", ") << newLine;
  507. mo << " }" << newLine;
  508. mo << " }" << newLine;
  509. mo << " }" << newLine;
  510. return mo.toString();
  511. }
  512. String getAndroidBuildTypes() const
  513. {
  514. MemoryOutputStream mo;
  515. mo << " buildTypes {" << newLine;
  516. int numDebugConfigs = 0;
  517. const int numConfigs = getNumConfigurations();
  518. for (int i = 0; i < numConfigs; ++i)
  519. {
  520. auto config = getConfiguration(i);
  521. if (config->isDebug()) numDebugConfigs++;
  522. if (numDebugConfigs > 1 || ((numConfigs - numDebugConfigs) > 1))
  523. continue;
  524. mo << " " << (config->isDebug() ? "debug" : "release") << " {" << newLine;
  525. mo << " initWith " << (config->isDebug() ? "debug" : "release") << newLine;
  526. mo << " debuggable " << (config->isDebug() ? "true" : "false") << newLine;
  527. mo << " jniDebuggable " << (config->isDebug() ? "true" : "false") << newLine;
  528. if (! config->isDebug())
  529. mo << " signingConfig signingConfigs.release" << newLine;
  530. mo << " }" << newLine;
  531. }
  532. mo << " }" << newLine;
  533. return mo.toString();
  534. }
  535. String getAndroidVariantFilter() const
  536. {
  537. MemoryOutputStream mo;
  538. mo << " variantFilter { variant ->" << newLine;
  539. mo << " def names = variant.flavors*.name" << newLine;
  540. for (ConstConfigIterator config (*this); config.next();)
  541. {
  542. const auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  543. mo << " if (names.contains (\"" << cfg.getProductFlavourNameIdentifier() << "\")" << newLine;
  544. mo << " && variant.buildType.name != \"" << (cfg.isDebug() ? "debug" : "release") << "\") {" << newLine;
  545. mo << " setIgnore(true)" << newLine;
  546. mo << " }" << newLine;
  547. }
  548. mo << " }" << newLine;
  549. return mo.toString();
  550. }
  551. String getAndroidRepositories() const
  552. {
  553. MemoryOutputStream mo;
  554. juce::StringArray repositories;
  555. repositories.addLines (androidRepositories.get());
  556. mo << "repositories {" << newLine;
  557. for (const auto& r : repositories)
  558. mo << " " << r << newLine;
  559. mo << "}" << newLine;
  560. return mo.toString();
  561. }
  562. String getAndroidDependencies() const
  563. {
  564. MemoryOutputStream mo;
  565. juce::StringArray dependencies;
  566. dependencies.addLines (androidDependencies.get());
  567. mo << "dependencies {" << newLine;
  568. for (const auto& d : dependencies)
  569. mo << " " << d << newLine;
  570. mo << "}" << newLine;
  571. return mo.toString();
  572. }
  573. //==============================================================================
  574. String getLocalPropertiesFileContent() const
  575. {
  576. String props;
  577. props << "ndk.dir=" << sanitisePath (ndkPath.toString()) << newLine
  578. << "sdk.dir=" << sanitisePath (sdkPath.toString()) << newLine;
  579. return props;
  580. }
  581. String getGradleWrapperPropertiesFileContent() const
  582. {
  583. String props;
  584. props << "distributionUrl=https\\://services.gradle.org/distributions/gradle-"
  585. << gradleVersion.get() << "-all.zip";
  586. return props;
  587. }
  588. //==============================================================================
  589. void createBaseExporterProperties (PropertyListBuilder& props)
  590. {
  591. static const char* orientations[] = { "Portrait and Landscape", "Portrait", "Landscape", nullptr };
  592. static const char* orientationValues[] = { "unspecified", "portrait", "landscape", nullptr };
  593. props.add (new TextPropertyComponent (androidRepositories.getPropertyAsValue(), "Module repositories", 32768, true),
  594. "Module repositories (one per line). These will be added to module-level gradle file repositories section. ");
  595. props.add (new TextPropertyComponent (androidDependencies.getPropertyAsValue(), "Module dependencies", 32768, true),
  596. "Module dependencies (one per line). These will be added to module-level gradle file dependencies section. ");
  597. props.add (new ChoicePropertyComponent (androidScreenOrientation.getPropertyAsValue(), "Screen orientation", StringArray (orientations), Array<var> (orientationValues)),
  598. "The screen orientations that this app should support");
  599. props.add (new TextWithDefaultPropertyComponent<String> (androidActivityClass, "Android Activity class name", 256),
  600. "The full java class name to use for the app's Activity class.");
  601. props.add (new TextPropertyComponent (androidActivitySubClassName.getPropertyAsValue(), "Android Activity sub-class name", 256, false),
  602. "If not empty, specifies the Android Activity class name stored in the app's manifest. "
  603. "Use this if you would like to use your own Android Activity sub-class.");
  604. props.add (new TextWithDefaultPropertyComponent<String> (androidVersionCode, "Android Version Code", 32),
  605. "An integer value that represents the version of the application code, relative to other versions.");
  606. props.add (new DependencyPathPropertyComponent (project.getFile().getParentDirectory(), sdkPath, "Android SDK Path"),
  607. "The path to the Android SDK folder on the target build machine");
  608. props.add (new DependencyPathPropertyComponent (project.getFile().getParentDirectory(), ndkPath, "Android NDK Path"),
  609. "The path to the Android NDK folder on the target build machine");
  610. props.add (new TextWithDefaultPropertyComponent<String> (androidMinimumSDK, "Minimum SDK version", 32),
  611. "The number of the minimum version of the Android SDK that the app requires");
  612. props.add (new TextPropertyComponent (androidExtraAssetsFolder.getPropertyAsValue(), "Extra Android Assets", 256, false),
  613. "A path to a folder (relative to the project folder) which conatins extra android assets.");
  614. }
  615. //==============================================================================
  616. void createManifestExporterProperties (PropertyListBuilder& props)
  617. {
  618. props.add (new BooleanPropertyComponent (androidInternetNeeded.getPropertyAsValue(), "Internet Access", "Specify internet access permission in the manifest"),
  619. "If enabled, this will set the android.permission.INTERNET flag in the manifest.");
  620. props.add (new BooleanPropertyComponent (androidMicNeeded.getPropertyAsValue(), "Audio Input Required", "Specify audio record permission in the manifest"),
  621. "If enabled, this will set the android.permission.RECORD_AUDIO flag in the manifest.");
  622. props.add (new BooleanPropertyComponent (androidBluetoothNeeded.getPropertyAsValue(), "Bluetooth permissions Required", "Specify bluetooth permission (required for Bluetooth MIDI)"),
  623. "If enabled, this will set the android.permission.BLUETOOTH and android.permission.BLUETOOTH_ADMIN flag in the manifest. This is required for Bluetooth MIDI on Android.");
  624. props.add (new BooleanPropertyComponent (androidExternalReadPermission.getPropertyAsValue(), "Read from external storage", "Specify permissions to read from external storage"),
  625. "If enabled, this will set the android.permission.READ_EXTERNAL_STORAGE flag in the manifest.");
  626. props.add (new BooleanPropertyComponent (androidExternalWritePermission.getPropertyAsValue(), "Write to external storage", "Specify permissions to write to external storage"),
  627. "If enabled, this will set the android.permission.WRITE_EXTERNAL_STORAGE flag in the manifest.");
  628. props.add (new TextPropertyComponent (androidOtherPermissions.getPropertyAsValue(), "Custom permissions", 2048, false),
  629. "A space-separated list of other permission flags that should be added to the manifest.");
  630. props.add (new TextPropertyComponent (androidManifestCustomXmlElements.getPropertyAsValue(), "Custom manifest xml elements", 8192, true),
  631. "You can specify custom XML elements that will be added to AndroidManifest.xml as children of <application> element.");
  632. }
  633. //==============================================================================
  634. void createCodeSigningExporterProperties (PropertyListBuilder& props)
  635. {
  636. props.add (new TextWithDefaultPropertyComponent<String> (androidKeyStore, "Key Signing: key.store", 2048),
  637. "The key.store value, used when signing the release package.");
  638. props.add (new TextWithDefaultPropertyComponent<String> (androidKeyStorePass, "Key Signing: key.store.password", 2048),
  639. "The key.store password, used when signing the release package.");
  640. props.add (new TextWithDefaultPropertyComponent<String> (androidKeyAlias, "Key Signing: key.alias", 2048),
  641. "The key.alias value, used when signing the release package.");
  642. props.add (new TextWithDefaultPropertyComponent<String> (androidKeyAliasPass, "Key Signing: key.alias.password", 2048),
  643. "The key.alias password, used when signing the release package.");
  644. }
  645. //==============================================================================
  646. void createOtherExporterProperties (PropertyListBuilder& props)
  647. {
  648. props.add (new TextPropertyComponent (androidTheme.getPropertyAsValue(), "Android Theme", 256, false),
  649. "E.g. @android:style/Theme.NoTitleBar or leave blank for default");
  650. static const char* cppStandardNames[] = { "C++03", "C++11", "C++14", nullptr };
  651. static const char* cppStandardValues[] = { "-std=c++03", "-std=c++11", "-std=c++14", nullptr };
  652. props.add (new ChoicePropertyComponent (getCppStandardValue(), "C++ standard to use",
  653. StringArray (cppStandardNames), Array<var> (cppStandardValues)),
  654. "The C++ standard to specify in the makefile");
  655. }
  656. //==============================================================================
  657. Value getCppStandardValue() { return getSetting (Ids::cppLanguageStandard); }
  658. String getCppStandardString() const { return settings[Ids::cppLanguageStandard]; }
  659. //==============================================================================
  660. String createDefaultClassName() const
  661. {
  662. auto s = project.getBundleIdentifier().toString().toLowerCase();
  663. if (s.length() > 5
  664. && s.containsChar ('.')
  665. && s.containsOnly ("abcdefghijklmnopqrstuvwxyz_.")
  666. && ! s.startsWithChar ('.'))
  667. {
  668. if (! s.endsWithChar ('.'))
  669. s << ".";
  670. }
  671. else
  672. {
  673. s = "com.yourcompany.";
  674. }
  675. return s + CodeHelpers::makeValidIdentifier (project.getProjectFilenameRoot(), false, true, false);
  676. }
  677. void initialiseDependencyPathValues()
  678. {
  679. sdkPath.referTo (Value (new DependencyPathValueSource (getSetting (Ids::androidSDKPath), Ids::androidSDKPath, TargetOS::getThisOS())));
  680. ndkPath.referTo (Value (new DependencyPathValueSource (getSetting (Ids::androidNDKPath), Ids::androidNDKPath, TargetOS::getThisOS())));
  681. }
  682. //==============================================================================
  683. void copyActivityJavaFiles (const OwnedArray<LibraryModule>& modules, const File& targetFolder, const String& package) const
  684. {
  685. if (androidActivityClass.get().contains ("_"))
  686. throw SaveError ("Your Android activity class name or path may not contain any underscores! Try a project name without underscores.");
  687. const String className (getActivityName());
  688. if (className.isEmpty())
  689. throw SaveError ("Invalid Android Activity class name: " + androidActivityClass.get());
  690. createDirectoryOrThrow (targetFolder);
  691. if (auto* coreModule = getCoreModule (modules))
  692. {
  693. File javaDestFile (targetFolder.getChildFile (className + ".java"));
  694. File javaSourceFolder (coreModule->getFolder().getChildFile ("native")
  695. .getChildFile ("java"));
  696. String juceMidiCode, juceMidiImports, juceRuntimePermissionsCode;
  697. juceMidiImports << newLine;
  698. if (androidMinimumSDK.get().getIntValue() >= 23)
  699. {
  700. File javaAndroidMidi (javaSourceFolder.getChildFile ("AndroidMidi.java"));
  701. File javaRuntimePermissions (javaSourceFolder.getChildFile ("AndroidRuntimePermissions.java"));
  702. juceMidiImports << "import android.media.midi.*;" << newLine
  703. << "import android.bluetooth.*;" << newLine
  704. << "import android.bluetooth.le.*;" << newLine;
  705. juceMidiCode = javaAndroidMidi.loadFileAsString().replace ("JuceAppActivity", className);
  706. juceRuntimePermissionsCode = javaRuntimePermissions.loadFileAsString().replace ("JuceAppActivity", className);
  707. }
  708. else
  709. {
  710. juceMidiCode = javaSourceFolder.getChildFile ("AndroidMidiFallback.java")
  711. .loadFileAsString()
  712. .replace ("JuceAppActivity", className);
  713. }
  714. auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java");
  715. auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString());
  716. {
  717. MemoryOutputStream newFile;
  718. for (const auto& line : javaSourceLines)
  719. {
  720. if (line.contains ("$$JuceAndroidMidiImports$$"))
  721. newFile << juceMidiImports;
  722. else if (line.contains ("$$JuceAndroidMidiCode$$"))
  723. newFile << juceMidiCode;
  724. else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$"))
  725. newFile << juceRuntimePermissionsCode;
  726. else
  727. newFile << line.replace ("JuceAppActivity", className)
  728. .replace ("package com.juce;", "package " + package + ";") << newLine;
  729. }
  730. javaSourceLines = StringArray::fromLines (newFile.toString());
  731. }
  732. while (javaSourceLines.size() > 2
  733. && javaSourceLines[javaSourceLines.size() - 1].trim().isEmpty()
  734. && javaSourceLines[javaSourceLines.size() - 2].trim().isEmpty())
  735. javaSourceLines.remove (javaSourceLines.size() - 1);
  736. overwriteFileIfDifferentOrThrow (javaDestFile, javaSourceLines.joinIntoString (newLine));
  737. }
  738. }
  739. void copyExtraResourceFiles() const
  740. {
  741. for (ConstConfigIterator config (*this); config.next();)
  742. {
  743. const auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  744. const juce::String path = cfg.isDebug() ? "app/src/debug/res/values" : "app/src/release/res/values";
  745. copyExtraResourceFiles (cfg.getAdditionalXmlResources(), path);
  746. }
  747. }
  748. void copyExtraResourceFiles (const juce::String& xmlResources, const juce::String& dstRelativePath) const
  749. {
  750. juce::StringArray resourcePaths;
  751. resourcePaths.addTokens (xmlResources, true);
  752. const File parentFolder (getTargetFolder().getChildFile (dstRelativePath));
  753. parentFolder.createDirectory();
  754. for (const auto& path : resourcePaths)
  755. {
  756. juce::File file (getProject().getFile().getChildFile(path));
  757. jassert (file.existsAsFile());
  758. if (file.existsAsFile())
  759. file.copyFileTo (parentFolder.getChildFile (file.getFileName()));
  760. }
  761. }
  762. String getActivityName() const
  763. {
  764. return androidActivityClass.get().fromLastOccurrenceOf (".", false, false);
  765. }
  766. String getActivitySubClassName() const
  767. {
  768. String activityPath = androidActivitySubClassName.get();
  769. return (activityPath.isEmpty()) ? getActivityName() : activityPath.fromLastOccurrenceOf (".", false, false);
  770. }
  771. String getActivityClassPackage() const
  772. {
  773. return androidActivityClass.get().upToLastOccurrenceOf (".", false, false);
  774. }
  775. String getJNIActivityClassName() const
  776. {
  777. return androidActivityClass.get().replaceCharacter ('.', '/');
  778. }
  779. static LibraryModule* getCoreModule (const OwnedArray<LibraryModule>& modules)
  780. {
  781. for (int i = modules.size(); --i >= 0;)
  782. if (modules.getUnchecked(i)->getID() == "juce_core")
  783. return modules.getUnchecked(i);
  784. return nullptr;
  785. }
  786. //==============================================================================
  787. String getNativeModuleBinaryName (const AndroidBuildConfiguration& config) const
  788. {
  789. return (isLibrary() ? File::createLegalFileName (config.getTargetBinaryNameString().trim()) : "juce_jni");
  790. }
  791. String getAppPlatform() const
  792. {
  793. int ndkVersion = androidMinimumSDK.get().getIntValue();
  794. if (ndkVersion == 9)
  795. ndkVersion = 10; // (doesn't seem to be a version '9')
  796. return "android-" + String (ndkVersion);
  797. }
  798. //==============================================================================
  799. void writeStringsXML (const File& folder) const
  800. {
  801. for (ConstConfigIterator config (*this); config.next();)
  802. {
  803. XmlElement strings ("resources");
  804. XmlElement* resourceName = strings.createNewChildElement ("string");
  805. resourceName->setAttribute ("name", "app_name");
  806. resourceName->addTextElement (projectName);
  807. const auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  808. for (XmlElement* e = XmlDocument::parse (cfg.getCustomStringsXml()); e != nullptr; e = e->getNextElement())
  809. strings.addChildElement (e);
  810. const juce::String dir = cfg.isDebug() ? "debug" : "release";
  811. const juce::String subPath = "app/src/" + dir + "/res/values/string.xml";
  812. writeXmlOrThrow (strings, folder.getChildFile (subPath), "utf-8", 100, true);
  813. }
  814. }
  815. void writeAndroidManifest (const File& folder) const
  816. {
  817. ScopedPointer<XmlElement> manifest (createManifestXML());
  818. writeXmlOrThrow (*manifest, folder.getChildFile ("src/main/AndroidManifest.xml"), "utf-8", 100, true);
  819. }
  820. void writeIcon (const File& file, const Image& im) const
  821. {
  822. if (im.isValid())
  823. {
  824. createDirectoryOrThrow (file.getParentDirectory());
  825. PNGImageFormat png;
  826. MemoryOutputStream mo;
  827. if (! png.writeImageToStream (im, mo))
  828. throw SaveError ("Can't generate Android icon file");
  829. overwriteFileIfDifferentOrThrow (file, mo);
  830. }
  831. }
  832. void writeIcons (const File& folder) const
  833. {
  834. ScopedPointer<Drawable> bigIcon (getBigIcon());
  835. ScopedPointer<Drawable> smallIcon (getSmallIcon());
  836. if (bigIcon != nullptr && smallIcon != nullptr)
  837. {
  838. const int step = jmax (bigIcon->getWidth(), bigIcon->getHeight()) / 8;
  839. writeIcon (folder.getChildFile ("drawable-xhdpi/icon.png"), getBestIconForSize (step * 8, false));
  840. writeIcon (folder.getChildFile ("drawable-hdpi/icon.png"), getBestIconForSize (step * 6, false));
  841. writeIcon (folder.getChildFile ("drawable-mdpi/icon.png"), getBestIconForSize (step * 4, false));
  842. writeIcon (folder.getChildFile ("drawable-ldpi/icon.png"), getBestIconForSize (step * 3, false));
  843. }
  844. else if (Drawable* icon = bigIcon != nullptr ? bigIcon : smallIcon)
  845. {
  846. writeIcon (folder.getChildFile ("drawable-mdpi/icon.png"), rescaleImageForIcon (*icon, icon->getWidth()));
  847. }
  848. }
  849. void writeAppIcons (const File& folder) const
  850. {
  851. writeIcons (folder.getChildFile ("app/src/main/res/"));
  852. }
  853. static String sanitisePath (String path)
  854. {
  855. return expandHomeFolderToken (path).replace ("\\", "\\\\");
  856. }
  857. static String expandHomeFolderToken (const String& path)
  858. {
  859. String homeFolder = File::getSpecialLocation (File::userHomeDirectory).getFullPathName();
  860. return path.replace ("${user.home}", homeFolder)
  861. .replace ("~", homeFolder);
  862. }
  863. //==============================================================================
  864. void addCompileUnits (const Project::Item& projectItem, MemoryOutputStream& mo, Array<RelativePath>& excludeFromBuild) const
  865. {
  866. if (projectItem.isGroup())
  867. {
  868. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  869. addCompileUnits (projectItem.getChild(i), mo, excludeFromBuild);
  870. }
  871. else if (projectItem.shouldBeAddedToTargetProject())
  872. {
  873. const RelativePath file (projectItem.getFile(), getTargetFolder().getChildFile ("app"), RelativePath::buildTargetFolder);
  874. const ProjectType::Target::Type targetType = getProject().getTargetTypeFromFilePath (projectItem.getFile(), true);
  875. mo << " \"" << file.toUnixStyle() << "\"" << newLine;
  876. if ((! projectItem.shouldBeCompiled()) || (! shouldFileBeCompiledByDefault (file))
  877. || (getProject().getProjectType().isAudioPlugin()
  878. && targetType != ProjectType::Target::SharedCodeTarget
  879. && targetType != ProjectType::Target::StandalonePlugIn))
  880. excludeFromBuild.add (file);
  881. }
  882. }
  883. void addCompileUnits (MemoryOutputStream& mo, Array<RelativePath>& excludeFromBuild) const
  884. {
  885. for (int i = 0; i < getAllGroups().size(); ++i)
  886. addCompileUnits (getAllGroups().getReference(i), mo, excludeFromBuild);
  887. }
  888. //==============================================================================
  889. StringArray getCmakeDefinitions() const
  890. {
  891. const String toolchain = gradleToolchain.get();
  892. const bool isClang = (toolchain == "clang");
  893. StringArray cmakeArgs;
  894. cmakeArgs.add ("\"-DANDROID_TOOLCHAIN=" + toolchain + "\"");
  895. cmakeArgs.add ("\"-DANDROID_PLATFORM=" + getAppPlatform() + "\"");
  896. cmakeArgs.add (String ("\"-DANDROID_STL=") + (isClang ? "c++_static" : "gnustl_static") + "\"");
  897. cmakeArgs.add ("\"-DANDROID_CPP_FEATURES=exceptions rtti\"");
  898. cmakeArgs.add ("\"-DANDROID_ARM_MODE=arm\"");
  899. cmakeArgs.add ("\"-DANDROID_ARM_NEON=TRUE\"");
  900. return cmakeArgs;
  901. }
  902. //==============================================================================
  903. StringArray getAndroidCompilerFlags() const
  904. {
  905. StringArray cFlags;
  906. cFlags.add ("\"-fsigned-char\"");
  907. return cFlags;
  908. }
  909. StringArray getAndroidCxxCompilerFlags() const
  910. {
  911. StringArray cxxFlags (getAndroidCompilerFlags());
  912. String cppStandardToUse (getCppStandardString());
  913. if (cppStandardToUse.isEmpty())
  914. cppStandardToUse = "-std=c++11";
  915. cxxFlags.add ("\"" + cppStandardToUse + "\"");
  916. return cxxFlags;
  917. }
  918. StringArray getProjectCompilerFlags() const
  919. {
  920. StringArray cFlags (getAndroidCompilerFlags());
  921. cFlags.addArray (getEscapedFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), true)));
  922. return cFlags;
  923. }
  924. StringArray getProjectCxxCompilerFlags() const
  925. {
  926. StringArray cxxFlags (getAndroidCxxCompilerFlags());
  927. cxxFlags.addArray (getEscapedFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), true)));
  928. return cxxFlags;
  929. }
  930. //==============================================================================
  931. StringPairArray getAndroidPreprocessorDefs() const
  932. {
  933. StringPairArray defines;
  934. defines.set ("JUCE_ANDROID", "1");
  935. defines.set ("JUCE_ANDROID_API_VERSION", androidMinimumSDK.get());
  936. defines.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_'));
  937. defines.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\"" + getJNIActivityClassName() + "\"");
  938. if (supportsGLv3())
  939. defines.set ("JUCE_ANDROID_GL_ES_VERSION_3_0", "1");
  940. return defines;
  941. }
  942. StringPairArray getProjectPreprocessorDefs() const
  943. {
  944. StringPairArray defines (getAndroidPreprocessorDefs());
  945. return mergePreprocessorDefs (defines, getProject().getPreprocessorDefs());
  946. }
  947. StringPairArray getConfigPreprocessorDefs (const BuildConfiguration& config) const
  948. {
  949. StringPairArray cfgDefines (config.getUniquePreprocessorDefs());
  950. if (config.isDebug())
  951. {
  952. cfgDefines.set ("DEBUG", "1");
  953. cfgDefines.set ("_DEBUG", "1");
  954. }
  955. else
  956. {
  957. cfgDefines.set ("NDEBUG", "1");
  958. }
  959. return cfgDefines;
  960. }
  961. //==============================================================================
  962. StringArray getAndroidLibraries() const
  963. {
  964. StringArray libraries;
  965. libraries.add ("log");
  966. libraries.add ("android");
  967. libraries.add (supportsGLv3() ? "GLESv3" : "GLESv2");
  968. libraries.add ("EGL");
  969. return libraries;
  970. }
  971. //==============================================================================
  972. StringArray getHeaderSearchPaths (const BuildConfiguration& config) const
  973. {
  974. StringArray paths (extraSearchPaths);
  975. paths.addArray (config.getHeaderSearchPaths());
  976. paths = getCleanedStringArray (paths);
  977. return paths;
  978. }
  979. //==============================================================================
  980. String escapeDirectoryForCmake (const String& path) const
  981. {
  982. RelativePath relative =
  983. RelativePath (path, RelativePath::buildTargetFolder)
  984. .rebased (getTargetFolder(), getTargetFolder().getChildFile ("app"), RelativePath::buildTargetFolder);
  985. return relative.toUnixStyle();
  986. }
  987. void writeCmakePathLines (MemoryOutputStream& mo, const String& prefix, const String& firstLine, const StringArray& paths,
  988. const String& suffix = ")") const
  989. {
  990. if (paths.size() > 0)
  991. {
  992. mo << prefix << firstLine << newLine;
  993. for (auto& path : paths)
  994. mo << prefix << " \"" << escapeDirectoryForCmake (path) << "\"" << newLine;
  995. mo << prefix << suffix << newLine << newLine;
  996. }
  997. }
  998. static StringArray getEscapedPreprocessorDefs (const StringPairArray& defs)
  999. {
  1000. StringArray escapedDefs;
  1001. for (int i = 0; i < defs.size(); ++i)
  1002. {
  1003. String escaped ("\"-D" + defs.getAllKeys()[i]);
  1004. String value = defs.getAllValues()[i];
  1005. if (value.isNotEmpty())
  1006. {
  1007. value = value.replace ("\"", "\\\"");
  1008. if (value.containsChar (L' '))
  1009. value = "\\\"" + value + "\\\"";
  1010. escaped += ("=" + value + "\"");
  1011. }
  1012. escapedDefs.add (escaped);
  1013. }
  1014. return escapedDefs;
  1015. }
  1016. static StringArray getEscapedFlags (const StringArray& flags)
  1017. {
  1018. StringArray escaped;
  1019. for (auto& flag : flags)
  1020. escaped.add ("\"" + flag + "\"");
  1021. return escaped;
  1022. }
  1023. //==============================================================================
  1024. XmlElement* createManifestXML() const
  1025. {
  1026. XmlElement* manifest = new XmlElement ("manifest");
  1027. manifest->setAttribute ("xmlns:android", "http://schemas.android.com/apk/res/android");
  1028. manifest->setAttribute ("android:versionCode", androidVersionCode.get());
  1029. manifest->setAttribute ("android:versionName", project.getVersionString());
  1030. manifest->setAttribute ("package", getActivityClassPackage());
  1031. if (! isLibrary())
  1032. {
  1033. XmlElement* screens = manifest->createNewChildElement ("supports-screens");
  1034. screens->setAttribute ("android:smallScreens", "true");
  1035. screens->setAttribute ("android:normalScreens", "true");
  1036. screens->setAttribute ("android:largeScreens", "true");
  1037. screens->setAttribute ("android:anyDensity", "true");
  1038. }
  1039. XmlElement* sdk = manifest->createNewChildElement ("uses-sdk");
  1040. sdk->setAttribute ("android:minSdkVersion", androidMinimumSDK.get());
  1041. sdk->setAttribute ("android:targetSdkVersion", androidMinimumSDK.get());
  1042. {
  1043. const StringArray permissions (getPermissionsRequired());
  1044. for (int i = permissions.size(); --i >= 0;)
  1045. manifest->createNewChildElement ("uses-permission")->setAttribute ("android:name", permissions[i]);
  1046. }
  1047. if (project.getModules().isModuleEnabled ("juce_opengl"))
  1048. {
  1049. XmlElement* feature = manifest->createNewChildElement ("uses-feature");
  1050. feature->setAttribute ("android:glEsVersion", (androidMinimumSDK.get().getIntValue() >= 18 ? "0x00030000" : "0x00020000"));
  1051. feature->setAttribute ("android:required", "true");
  1052. }
  1053. if (! isLibrary())
  1054. {
  1055. XmlElement* app = manifest->createNewChildElement ("application");
  1056. app->setAttribute ("android:label", "@string/app_name");
  1057. if (androidTheme.get().isNotEmpty())
  1058. app->setAttribute ("android:theme", androidTheme.get());
  1059. {
  1060. ScopedPointer<Drawable> bigIcon (getBigIcon()), smallIcon (getSmallIcon());
  1061. if (bigIcon != nullptr || smallIcon != nullptr)
  1062. app->setAttribute ("android:icon", "@drawable/icon");
  1063. }
  1064. if (androidMinimumSDK.get().getIntValue() >= 11)
  1065. app->setAttribute ("android:hardwareAccelerated", "false"); // (using the 2D acceleration slows down openGL)
  1066. XmlElement* act = app->createNewChildElement ("activity");
  1067. act->setAttribute ("android:name", getActivitySubClassName());
  1068. act->setAttribute ("android:label", "@string/app_name");
  1069. String configChanges ("keyboardHidden|orientation");
  1070. if (androidMinimumSDK.get().getIntValue() >= 13)
  1071. configChanges += "|screenSize";
  1072. act->setAttribute ("android:configChanges", configChanges);
  1073. act->setAttribute ("android:screenOrientation", androidScreenOrientation.get());
  1074. XmlElement* intent = act->createNewChildElement ("intent-filter");
  1075. intent->createNewChildElement ("action")->setAttribute ("android:name", "android.intent.action.MAIN");
  1076. intent->createNewChildElement ("category")->setAttribute ("android:name", "android.intent.category.LAUNCHER");
  1077. for (XmlElement* e = XmlDocument::parse (androidManifestCustomXmlElements.get()); e != nullptr; e = e->getNextElement())
  1078. app->addChildElement (e);
  1079. }
  1080. return manifest;
  1081. }
  1082. StringArray getPermissionsRequired() const
  1083. {
  1084. StringArray s;
  1085. s.addTokens (androidOtherPermissions.get(), ", ", "");
  1086. if (androidInternetNeeded.get())
  1087. s.add ("android.permission.INTERNET");
  1088. if (androidMicNeeded.get())
  1089. s.add ("android.permission.RECORD_AUDIO");
  1090. if (androidBluetoothNeeded.get())
  1091. {
  1092. s.add ("android.permission.BLUETOOTH");
  1093. s.add ("android.permission.BLUETOOTH_ADMIN");
  1094. s.add ("android.permission.ACCESS_COARSE_LOCATION");
  1095. }
  1096. if (androidExternalReadPermission.get())
  1097. s.add ("android.permission.READ_EXTERNAL_STORAGE");
  1098. if (androidExternalWritePermission.get())
  1099. s.add ("android.permission.WRITE_EXTERNAL_STORAGE");
  1100. return getCleanedStringArray (s);
  1101. }
  1102. //==============================================================================
  1103. bool isLibrary() const
  1104. {
  1105. return getProject().getProjectType().isDynamicLibrary()
  1106. || getProject().getProjectType().isStaticLibrary();
  1107. }
  1108. static String toGradleList (const StringArray& array)
  1109. {
  1110. StringArray escapedArray;
  1111. for (auto element : array)
  1112. escapedArray.add ("\"" + element.replace ("\\", "\\\\").replace ("\"", "\\\"") + "\"");
  1113. return escapedArray.joinIntoString (", ");
  1114. }
  1115. bool supportsGLv3() const
  1116. {
  1117. return (androidMinimumSDK.get().getIntValue() >= 18);
  1118. }
  1119. //==============================================================================
  1120. Value sdkPath, ndkPath;
  1121. const File AndroidExecutable;
  1122. JUCE_DECLARE_NON_COPYABLE (AndroidProjectExporter)
  1123. };
  1124. ProjectExporter* createAndroidExporter (Project& p, const ValueTree& t)
  1125. {
  1126. return new AndroidProjectExporter (p, t);
  1127. }
  1128. ProjectExporter* createAndroidExporterForSetting (Project& p, const ValueTree& t)
  1129. {
  1130. return AndroidProjectExporter::createForSettings (p, t);
  1131. }