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.

460 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2016 - 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 AndroidAntProjectExporter : public AndroidProjectExporterBase
  18. {
  19. public:
  20. //==============================================================================
  21. bool canLaunchProject() override { return false; }
  22. bool launchProject() override { return false; }
  23. bool isAndroid() const override { return true; }
  24. bool usesMMFiles() const override { return false; }
  25. bool canCopeWithDuplicateFiles() override { return false; }
  26. bool isAndroidStudio() override { return false; }
  27. bool isAndroidAnt() override { return true; }
  28. //==============================================================================
  29. static const char* getName() { return "Android Ant Project"; }
  30. static const char* getValueTreeTypeName() { return "ANDROID"; }
  31. //==============================================================================
  32. Value getNDKToolchainVersionValue() { return getSetting (Ids::toolset); }
  33. String getNDKToolchainVersionString() const { return settings [Ids::toolset]; }
  34. Value getStaticLibrariesValue() { return getSetting (Ids::androidStaticLibraries); }
  35. String getStaticLibrariesString() const { return settings [Ids::androidStaticLibraries]; }
  36. Value getSharedLibrariesValue() { return getSetting (Ids::androidSharedLibraries); }
  37. String getSharedLibrariesString() const { return settings [Ids::androidSharedLibraries]; }
  38. //==============================================================================
  39. static AndroidAntProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  40. {
  41. if (settings.hasType (getValueTreeTypeName()))
  42. return new AndroidAntProjectExporter (project, settings);
  43. return nullptr;
  44. }
  45. //==============================================================================
  46. AndroidAntProjectExporter (Project& p, const ValueTree& t)
  47. : AndroidProjectExporterBase (p, t)
  48. {
  49. name = getName();
  50. if (getTargetLocationString().isEmpty())
  51. getTargetLocationValue() = getDefaultBuildsRootFolder() + "Android";
  52. }
  53. //==============================================================================
  54. void createToolchainExporterProperties (PropertyListBuilder& props) override
  55. {
  56. props.add (new TextPropertyComponent (getNDKToolchainVersionValue(), "NDK Toolchain version", 32, false),
  57. "The variable NDK_TOOLCHAIN_VERSION in Application.mk - leave blank for a default value");
  58. }
  59. void createLibraryModuleExporterProperties (PropertyListBuilder& props) override
  60. {
  61. props.add (new TextPropertyComponent (getStaticLibrariesValue(), "Import static library modules", 8192, true),
  62. "Comma or whitespace delimited list of static libraries (.a) defined in NDK_MODULE_PATH.");
  63. props.add (new TextPropertyComponent (getSharedLibrariesValue(), "Import shared library modules", 8192, true),
  64. "Comma or whitespace delimited list of shared libraries (.so) defined in NDK_MODULE_PATH.");
  65. }
  66. //==============================================================================
  67. void create (const OwnedArray<LibraryModule>& modules) const override
  68. {
  69. AndroidProjectExporterBase::create (modules);
  70. const File target (getTargetFolder());
  71. const File jniFolder (target.getChildFile ("jni"));
  72. createDirectoryOrThrow (jniFolder);
  73. createDirectoryOrThrow (target.getChildFile ("res").getChildFile ("values"));
  74. createDirectoryOrThrow (target.getChildFile ("libs"));
  75. createDirectoryOrThrow (target.getChildFile ("bin"));
  76. {
  77. ScopedPointer<XmlElement> manifest (createManifestXML());
  78. writeXmlOrThrow (*manifest, target.getChildFile ("AndroidManifest.xml"), "utf-8", 100, true);
  79. }
  80. writeApplicationMk (jniFolder.getChildFile ("Application.mk"));
  81. writeAndroidMk (jniFolder.getChildFile ("Android.mk"));
  82. {
  83. ScopedPointer<XmlElement> antBuildXml (createAntBuildXML());
  84. writeXmlOrThrow (*antBuildXml, target.getChildFile ("build.xml"), "UTF-8", 100);
  85. }
  86. writeProjectPropertiesFile (target.getChildFile ("project.properties"));
  87. writeLocalPropertiesFile (target.getChildFile ("local.properties"));
  88. writeStringsFile (target.getChildFile ("res/values/strings.xml"));
  89. writeIcons (target.getChildFile ("res"));
  90. }
  91. //==============================================================================
  92. class AndroidBuildConfiguration : public BuildConfiguration
  93. {
  94. public:
  95. AndroidBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
  96. : BuildConfiguration (p, settings, e)
  97. {
  98. if (getArchitectures().isEmpty())
  99. {
  100. if (isDebug())
  101. getArchitecturesValue() = "armeabi x86";
  102. else
  103. getArchitecturesValue() = "armeabi armeabi-v7a x86";
  104. }
  105. }
  106. Value getArchitecturesValue() { return getValue (Ids::androidArchitectures); }
  107. String getArchitectures() const { return config [Ids::androidArchitectures]; }
  108. var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); }
  109. void createConfigProperties (PropertyListBuilder& props) override
  110. {
  111. addGCCOptimisationProperty (props);
  112. props.add (new TextPropertyComponent (getArchitecturesValue(), "Architectures", 256, false),
  113. "A list of the ARM architectures to build (for a fat binary).");
  114. }
  115. };
  116. BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override
  117. {
  118. return new AndroidBuildConfiguration (project, v, *this);
  119. }
  120. private:
  121. //==============================================================================
  122. String getToolchainVersion() const
  123. {
  124. String v (getNDKToolchainVersionString());
  125. return v.isNotEmpty() ? v : "4.8";
  126. }
  127. //==============================================================================
  128. String getCppFlags() const
  129. {
  130. String flags ("-fsigned-char -fexceptions -frtti");
  131. if (! getNDKToolchainVersionString().startsWithIgnoreCase ("clang"))
  132. flags << " -Wno-psabi";
  133. return flags;
  134. }
  135. String getAppPlatform() const
  136. {
  137. int ndkVersion = getMinimumSDKVersionString().getIntValue();
  138. if (ndkVersion == 9)
  139. ndkVersion = 10; // (doesn't seem to be a version '9')
  140. return "android-" + String (ndkVersion);
  141. }
  142. void writeApplicationMk (const File& file) const
  143. {
  144. MemoryOutputStream mo;
  145. mo << "# Automatically generated makefile, created by the Introjucer" << newLine
  146. << "# Don't edit this file! Your changes will be overwritten when you re-save the Introjucer project!" << newLine
  147. << newLine
  148. << "APP_STL := gnustl_static" << newLine
  149. << "APP_CPPFLAGS += " << getCppFlags() << newLine
  150. << "APP_PLATFORM := " << getAppPlatform() << newLine
  151. << "NDK_TOOLCHAIN_VERSION := " << getToolchainVersion() << newLine
  152. << newLine
  153. << "ifeq ($(NDK_DEBUG),1)" << newLine
  154. << " APP_ABI := " << getABIs<AndroidBuildConfiguration> (true) << newLine
  155. << "else" << newLine
  156. << " APP_ABI := " << getABIs<AndroidBuildConfiguration> (false) << newLine
  157. << "endif" << newLine;
  158. overwriteFileIfDifferentOrThrow (file, mo);
  159. }
  160. struct ShouldFileBeCompiledPredicate
  161. {
  162. bool operator() (const Project::Item& projectItem) const { return projectItem.shouldBeCompiled(); }
  163. };
  164. void writeAndroidMk (const File& file) const
  165. {
  166. Array<RelativePath> files;
  167. for (int i = 0; i < getAllGroups().size(); ++i)
  168. findAllProjectItemsWithPredicate (getAllGroups().getReference(i), files, ShouldFileBeCompiledPredicate());
  169. MemoryOutputStream mo;
  170. writeAndroidMk (mo, files);
  171. overwriteFileIfDifferentOrThrow (file, mo);
  172. }
  173. void writeAndroidMkVariableList (OutputStream& out, const String& variableName, const String& settingsValue) const
  174. {
  175. const StringArray separatedItems (getCommaOrWhitespaceSeparatedItems (settingsValue));
  176. if (separatedItems.size() > 0)
  177. out << newLine << variableName << " := " << separatedItems.joinIntoString (" ") << newLine;
  178. }
  179. void writeAndroidMk (OutputStream& out, const Array<RelativePath>& files) const
  180. {
  181. out << "# Automatically generated makefile, created by the Introjucer" << newLine
  182. << "# Don't edit this file! Your changes will be overwritten when you re-save the Introjucer project!" << newLine
  183. << newLine
  184. << "LOCAL_PATH := $(call my-dir)" << newLine
  185. << newLine
  186. << "include $(CLEAR_VARS)" << newLine
  187. << newLine
  188. << "ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)" << newLine
  189. << " LOCAL_ARM_MODE := arm" << newLine
  190. << "endif" << newLine
  191. << newLine
  192. << "LOCAL_MODULE := juce_jni" << newLine
  193. << "LOCAL_SRC_FILES := \\" << newLine;
  194. for (int i = 0; i < files.size(); ++i)
  195. out << " " << (files.getReference(i).isAbsolute() ? "" : "../")
  196. << escapeSpaces (files.getReference(i).toUnixStyle()) << "\\" << newLine;
  197. writeAndroidMkVariableList (out, "LOCAL_STATIC_LIBRARIES", getStaticLibrariesString());
  198. writeAndroidMkVariableList (out, "LOCAL_SHARED_LIBRARIES", getSharedLibrariesString());
  199. out << newLine
  200. << "ifeq ($(NDK_DEBUG),1)" << newLine;
  201. writeConfigSettings (out, true);
  202. out << "else" << newLine;
  203. writeConfigSettings (out, false);
  204. out << "endif" << newLine
  205. << newLine
  206. << "include $(BUILD_SHARED_LIBRARY)" << newLine;
  207. StringArray importModules (getCommaOrWhitespaceSeparatedItems (getStaticLibrariesString()));
  208. importModules.addArray (getCommaOrWhitespaceSeparatedItems (getSharedLibrariesString()));
  209. for (int i = 0; i < importModules.size(); ++i)
  210. out << "$(call import-module," << importModules[i] << ")" << newLine;
  211. }
  212. void writeConfigSettings (OutputStream& out, bool forDebug) const
  213. {
  214. for (ConstConfigIterator config (*this); config.next();)
  215. {
  216. if (config->isDebug() == forDebug)
  217. {
  218. const AndroidBuildConfiguration& androidConfig = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  219. String cppFlags;
  220. cppFlags << createCPPFlags (androidConfig)
  221. << (" " + replacePreprocessorTokens (androidConfig, getExtraCompilerFlagsString()).trim()).trimEnd()
  222. << newLine
  223. << getLDLIBS (androidConfig).trimEnd()
  224. << newLine;
  225. out << " LOCAL_CPPFLAGS += " << cppFlags;
  226. out << " LOCAL_CFLAGS += " << cppFlags;
  227. break;
  228. }
  229. }
  230. }
  231. String getLDLIBS (const AndroidBuildConfiguration& config) const
  232. {
  233. return " LOCAL_LDLIBS :=" + config.getGCCLibraryPathFlags()
  234. + " -llog -lGLESv2 -landroid -lEGL" + getExternalLibraryFlags (config)
  235. + " " + replacePreprocessorTokens (config, getExtraLinkerFlagsString());
  236. }
  237. String createIncludePathFlags (const BuildConfiguration& config) const
  238. {
  239. String flags;
  240. StringArray searchPaths (extraSearchPaths);
  241. searchPaths.addArray (config.getHeaderSearchPaths());
  242. searchPaths = getCleanedStringArray (searchPaths);
  243. for (int i = 0; i < searchPaths.size(); ++i)
  244. flags << " -I " << FileHelpers::unixStylePath (replacePreprocessorTokens (config, searchPaths[i])).quoted();
  245. return flags;
  246. }
  247. String createCPPFlags (const BuildConfiguration& config) const
  248. {
  249. StringPairArray defines;
  250. defines.set ("JUCE_ANDROID", "1");
  251. defines.set ("JUCE_ANDROID_API_VERSION", getMinimumSDKVersionString());
  252. defines.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_'));
  253. defines.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\\\"" + getJNIActivityClassName() + "\\\"");
  254. String flags ("-fsigned-char -fexceptions -frtti");
  255. if (config.isDebug())
  256. {
  257. flags << " -g";
  258. defines.set ("DEBUG", "1");
  259. defines.set ("_DEBUG", "1");
  260. }
  261. else
  262. {
  263. defines.set ("NDEBUG", "1");
  264. }
  265. flags << createIncludePathFlags (config)
  266. << " -O" << config.getGCCOptimisationFlag();
  267. flags << " -std=gnu++11";
  268. defines = mergePreprocessorDefs (defines, getAllPreprocessorDefs (config));
  269. return flags + createGCCPreprocessorFlags (defines);
  270. }
  271. //==============================================================================
  272. XmlElement* createAntBuildXML() const
  273. {
  274. XmlElement* proj = new XmlElement ("project");
  275. proj->setAttribute ("name", projectName);
  276. proj->setAttribute ("default", "debug");
  277. proj->createNewChildElement ("loadproperties")->setAttribute ("srcFile", "local.properties");
  278. proj->createNewChildElement ("loadproperties")->setAttribute ("srcFile", "project.properties");
  279. {
  280. XmlElement* target = proj->createNewChildElement ("target");
  281. target->setAttribute ("name", "clean");
  282. target->setAttribute ("depends", "android_rules.clean");
  283. target->createNewChildElement ("delete")->setAttribute ("dir", "libs");
  284. target->createNewChildElement ("delete")->setAttribute ("dir", "obj");
  285. XmlElement* executable = target->createNewChildElement ("exec");
  286. executable->setAttribute ("executable", "${ndk.dir}/ndk-build");
  287. executable->setAttribute ("dir", "${basedir}");
  288. executable->setAttribute ("failonerror", "true");
  289. executable->createNewChildElement ("arg")->setAttribute ("value", "clean");
  290. }
  291. {
  292. XmlElement* target = proj->createNewChildElement ("target");
  293. target->setAttribute ("name", "-pre-build");
  294. addDebugConditionClause (target, "makefileConfig", "Debug", "Release");
  295. addDebugConditionClause (target, "ndkDebugValue", "NDK_DEBUG=1", "NDK_DEBUG=0");
  296. String debugABIs, releaseABIs;
  297. for (ConstConfigIterator config (*this); config.next();)
  298. {
  299. const AndroidBuildConfiguration& androidConfig = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  300. if (config->isDebug())
  301. debugABIs = androidConfig.getArchitectures();
  302. else
  303. releaseABIs = androidConfig.getArchitectures();
  304. }
  305. addDebugConditionClause (target, "app_abis", debugABIs, releaseABIs);
  306. XmlElement* executable = target->createNewChildElement ("exec");
  307. executable->setAttribute ("executable", "${ndk.dir}/ndk-build");
  308. executable->setAttribute ("dir", "${basedir}");
  309. executable->setAttribute ("failonerror", "true");
  310. executable->createNewChildElement ("arg")->setAttribute ("value", "--jobs=4");
  311. executable->createNewChildElement ("arg")->setAttribute ("value", "CONFIG=${makefileConfig}");
  312. executable->createNewChildElement ("arg")->setAttribute ("value", "${ndkDebugValue}");
  313. executable->createNewChildElement ("arg")->setAttribute ("value", "APP_ABI=${app_abis}");
  314. target->createNewChildElement ("delete")->setAttribute ("file", "${out.final.file}");
  315. target->createNewChildElement ("delete")->setAttribute ("file", "${out.packaged.file}");
  316. }
  317. proj->createNewChildElement ("import")->setAttribute ("file", "${sdk.dir}/tools/ant/build.xml");
  318. return proj;
  319. }
  320. void addDebugConditionClause (XmlElement* target, const String& property,
  321. const String& debugValue, const String& releaseValue) const
  322. {
  323. XmlElement* condition = target->createNewChildElement ("condition");
  324. condition->setAttribute ("property", property);
  325. condition->setAttribute ("value", debugValue);
  326. condition->setAttribute ("else", releaseValue);
  327. XmlElement* equals = condition->createNewChildElement ("equals");
  328. equals->setAttribute ("arg1", "${ant.project.invoked-targets}");
  329. equals->setAttribute ("arg2", "debug");
  330. }
  331. void writeProjectPropertiesFile (const File& file) const
  332. {
  333. MemoryOutputStream mo;
  334. mo << "# This file is used to override default values used by the Ant build system." << newLine
  335. << "# It is automatically generated - DO NOT EDIT IT or your changes will be lost!." << newLine
  336. << newLine
  337. << "target=" << getAppPlatform() << newLine
  338. << newLine;
  339. overwriteFileIfDifferentOrThrow (file, mo);
  340. }
  341. void writeLocalPropertiesFile (const File& file) const
  342. {
  343. MemoryOutputStream mo;
  344. mo << "# This file is used to override default values used by the Ant build system." << newLine
  345. << "# It is automatically generated by the Introjucer - DO NOT EDIT IT or your changes will be lost!." << newLine
  346. << newLine
  347. << "sdk.dir=" << escapeSpaces (replacePreprocessorDefs (getAllPreprocessorDefs(), getSDKPathString())) << newLine
  348. << "ndk.dir=" << escapeSpaces (replacePreprocessorDefs (getAllPreprocessorDefs(), getNDKPathString())) << newLine
  349. << "key.store=" << getKeyStoreString() << newLine
  350. << "key.alias=" << getKeyAliasString() << newLine
  351. << "key.store.password=" << getKeyStorePassString() << newLine
  352. << "key.alias.password=" << getKeyAliasPassString() << newLine
  353. << newLine;
  354. overwriteFileIfDifferentOrThrow (file, mo);
  355. }
  356. void writeStringsFile (const File& file) const
  357. {
  358. XmlElement strings ("resources");
  359. XmlElement* resourceName = strings.createNewChildElement ("string");
  360. resourceName->setAttribute ("name", "app_name");
  361. resourceName->addTextElement (projectName);
  362. writeXmlOrThrow (strings, file, "utf-8", 100);
  363. }
  364. //==============================================================================
  365. JUCE_DECLARE_NON_COPYABLE (AndroidAntProjectExporter)
  366. };