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.

1049 lines
49KB

  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. #include "jucer_ProjectExport_CodeBlocks.h"
  20. #include "jucer_ProjectExport_Make.h"
  21. #include "jucer_ProjectExport_Xcode.h"
  22. class CLionProjectExporter : public ProjectExporter
  23. {
  24. protected:
  25. //==============================================================================
  26. class CLionBuildConfiguration : public BuildConfiguration
  27. {
  28. public:
  29. CLionBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
  30. : BuildConfiguration (p, settings, e)
  31. {
  32. }
  33. void createConfigProperties (PropertyListBuilder&) override {}
  34. var getDefaultOptimisationLevel() const override { return {}; }
  35. String getModuleLibraryArchName() const override { return {}; }
  36. };
  37. BuildConfiguration::Ptr createBuildConfig (const ValueTree& tree) const override
  38. {
  39. return new CLionBuildConfiguration (project, tree, *this);
  40. }
  41. public:
  42. //==============================================================================
  43. static const char* getName() { return "CLion (beta)"; }
  44. static const char* getValueTreeTypeName() { return "CLION"; }
  45. static CLionProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  46. {
  47. if (settings.hasType (getValueTreeTypeName()))
  48. return new CLionProjectExporter (project, settings);
  49. return nullptr;
  50. }
  51. static bool isExporterSupported (const ProjectExporter& exporter)
  52. {
  53. return exporter.isMakefile()
  54. || (exporter.isXcode() && ! exporter.isiOS())
  55. || (exporter.isCodeBlocks() && exporter.isWindows());
  56. }
  57. //==============================================================================
  58. CLionProjectExporter (Project& p, const ValueTree& t) : ProjectExporter (p, t)
  59. {
  60. name = getName();
  61. if (getTargetLocationString().isEmpty())
  62. getTargetLocationValue() = getDefaultBuildsRootFolder() + "CLion";
  63. }
  64. //==============================================================================
  65. bool usesMMFiles() const override { return false; }
  66. bool canCopeWithDuplicateFiles() override { return false; }
  67. bool supportsUserDefinedConfigurations() const override { return false; }
  68. bool isXcode() const override { return false; }
  69. bool isVisualStudio() const override { return false; }
  70. bool isCodeBlocks() const override { return false; }
  71. bool isMakefile() const override { return false; }
  72. bool isAndroidStudio() const override { return false; }
  73. bool isCLion() const override { return true; }
  74. bool isAndroid() const override { return false; }
  75. bool isWindows() const override { return false; }
  76. bool isLinux() const override { return false; }
  77. bool isOSX() const override { return false; }
  78. bool isiOS() const override { return false; }
  79. bool supportsTargetType (ProjectType::Target::Type) const override { return true; }
  80. void addPlatformSpecificSettingsForProjectType (const ProjectType&) override {}
  81. void initialiseDependencyPathValues() override {}
  82. //==============================================================================
  83. bool canLaunchProject() override
  84. {
  85. #if JUCE_MAC
  86. static Identifier exporterName ("XCODE_MAC");
  87. #elif JUCE_WINDOWS
  88. static Identifier exporterName ("CODEBLOCKS_WINDOWS");
  89. #else
  90. static Identifier exporterName;
  91. #endif
  92. if (getProject().getExporters().getChildWithName (exporterName).isValid())
  93. return getCLionExecutable().existsAsFile();
  94. return false;
  95. }
  96. bool launchProject() override
  97. {
  98. return getCLionExecutable().startAsProcess (getTargetFolder().getFullPathName());
  99. }
  100. String getDescription() override
  101. {
  102. String description;
  103. description << "The " << getName() << " exporter produces a single CMakeLists.txt file with "
  104. << "multiple platform dependant sections, where the configuration for each section "
  105. << "is inherited from other exporters added to this project." << newLine
  106. << newLine
  107. << "The exporters which provide the CLion configuration for the corresponding platform are:" << newLine
  108. << newLine;
  109. for (auto& exporterName : getExporterNames())
  110. {
  111. ScopedPointer<ProjectExporter> exporter = createNewExporter (getProject(), exporterName);
  112. if (isExporterSupported (*exporter))
  113. description << exporter->getName() << newLine;
  114. }
  115. description << newLine
  116. << "Add these exporters to the project to enable CLion builds." << newLine
  117. << newLine
  118. << "Not all features of all the exporters are currently supported. Notable omissions are AUv3 "
  119. << "plug-ins, embedding resources and fat binaries on MacOS, and adding application icons. On "
  120. << "Windows CLion requires a GCC-based compiler like MinGW.";
  121. return description;
  122. }
  123. void createExporterProperties (PropertyListBuilder& properties) override
  124. {
  125. for (Project::ExporterIterator exporter (getProject()); exporter.next();)
  126. if (isExporterSupported (*exporter))
  127. properties.add (new BooleanPropertyComponent (getExporterEnabledValue (*exporter),
  128. "Import settings from exporter",
  129. exporter->getName()),
  130. "If this is enabled then settings from the corresponding exporter will "
  131. "be used in the generated CMakeLists.txt");
  132. }
  133. void createDefaultConfigs() override {}
  134. void create (const OwnedArray<LibraryModule>&) const override
  135. {
  136. MemoryOutputStream out;
  137. out << "# Automatically generated CMakeLists, created by the Projucer" << newLine
  138. << "# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!" << newLine
  139. << newLine;
  140. out << "cmake_minimum_required (VERSION 3.4.1)" << newLine
  141. << newLine;
  142. out << "if (NOT CMAKE_BUILD_TYPE)" << newLine
  143. << " set (CMAKE_BUILD_TYPE \"Debug\" CACHE STRING \"Choose the type of build.\" FORCE)" << newLine
  144. << "endif (NOT CMAKE_BUILD_TYPE)" << newLine
  145. << newLine;
  146. // We'll append to this later.
  147. overwriteFileIfDifferentOrThrow (getTargetFolder().getChildFile ("CMakeLists.txt"), out);
  148. }
  149. void writeCMakeListsExporterSection (ProjectExporter* exporter) const
  150. {
  151. if (! (isExporterSupported (*exporter) && isExporterEnabled (*exporter)))
  152. return;
  153. MemoryBlock existingContent;
  154. getTargetFolder().getChildFile ("CMakeLists.txt").loadFileAsData (existingContent);
  155. MemoryOutputStream out (existingContent, true);
  156. out << "###############################################################################" << newLine
  157. << "# " << exporter->getName() << newLine
  158. << "###############################################################################" << newLine
  159. << newLine;
  160. if (auto* makefileExporter = dynamic_cast<MakefileProjectExporter*> (exporter))
  161. {
  162. out << "if (UNIX AND NOT APPLE)" << newLine << newLine;
  163. writeCMakeListsMakefileSection (out, *makefileExporter);
  164. }
  165. else if (auto* xcodeExporter = dynamic_cast<XcodeProjectExporter*> (exporter))
  166. {
  167. out << "if (APPLE)" << newLine << newLine;
  168. writeCMakeListsXcodeSection (out, *xcodeExporter);
  169. }
  170. else if (auto* codeBlocksExporter = dynamic_cast<CodeBlocksProjectExporter*> (exporter))
  171. {
  172. out << "if (WIN32)" << newLine << newLine;
  173. writeCMakeListsCodeBlocksSection (out, *codeBlocksExporter);
  174. }
  175. out << "endif()" << newLine << newLine;
  176. overwriteFileIfDifferentOrThrow (getTargetFolder().getChildFile ("CMakeLists.txt"), out);
  177. }
  178. private:
  179. //==============================================================================
  180. static File getCLionExecutable()
  181. {
  182. #if JUCE_MAC
  183. return { "/Applications/CLion.app/Contents/MacOS/clion" };
  184. #elif JUCE_WINDOWS
  185. auto regValue = WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\Applications\\clion64.exe\\shell\\open\\command\\", {}, {});
  186. auto openCmd = StringArray::fromTokens (regValue, true);
  187. if (! openCmd.isEmpty())
  188. return { openCmd[0].unquoted() };
  189. #endif
  190. return {};
  191. }
  192. //==============================================================================
  193. Identifier getExporterEnabledId (const ProjectExporter& exporter) const
  194. {
  195. jassert (isExporterSupported (exporter));
  196. if (exporter.isMakefile()) return Ids::clionMakefileEnabled;
  197. else if (exporter.isXcode()) return Ids::clionXcodeEnabled;
  198. else if (exporter.isCodeBlocks()) return Ids::clionCodeBlocksEnabled;
  199. jassertfalse;
  200. return {};
  201. }
  202. bool isExporterEnabled (const ProjectExporter& exporter) const
  203. {
  204. auto setting = settings[getExporterEnabledId (exporter)];
  205. return setting.isVoid() || setting;
  206. }
  207. Value getExporterEnabledValue (const ProjectExporter& exporter)
  208. {
  209. auto enabledID = getExporterEnabledId (exporter);
  210. getSetting (enabledID) = isExporterEnabled (exporter);
  211. return getSetting (enabledID);
  212. }
  213. //==============================================================================
  214. static String setCMakeVariable (const String& variableName, const String& value)
  215. {
  216. return String ("set (") + variableName + " \"" + value + "\")";
  217. }
  218. static String addToCMakeVariable (const String& variableName, const String& value)
  219. {
  220. return setCMakeVariable (variableName, "${" + variableName + "} " + value);
  221. }
  222. static String getTargetVarName (ProjectType::Target& target)
  223. {
  224. return String (target.getName()).toUpperCase().replaceCharacter (L' ', L'_');
  225. }
  226. template <class Target, class Exporter>
  227. void getFileInfoList (Target& target, Exporter& exporter, const Project::Item& projectItem, std::vector<std::pair<String, bool>>& fileInfoList) const
  228. {
  229. const auto targetType = (getProject().getProjectType().isAudioPlugin() ? target.type : Target::Type::SharedCodeTarget);
  230. if (projectItem.isGroup())
  231. {
  232. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  233. getFileInfoList (target, exporter, projectItem.getChild(i), fileInfoList);
  234. }
  235. else if (projectItem.shouldBeAddedToTargetProject() && getProject().getTargetTypeFromFilePath (projectItem.getFile(), true) == targetType )
  236. {
  237. const auto path = RelativePath (projectItem.getFile(), exporter.getTargetFolder(), RelativePath::buildTargetFolder).toUnixStyle();
  238. fileInfoList.push_back ({ path, projectItem.shouldBeCompiled() });
  239. }
  240. }
  241. template <class Exporter>
  242. void writeCMakeTargets (OutputStream& out, Exporter& exporter) const
  243. {
  244. for (auto* target : exporter.targets)
  245. {
  246. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::macOSAppex
  247. || target->type == ProjectType::Target::Type::AggregateTarget
  248. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  249. continue;
  250. String functionName;
  251. StringArray properties;
  252. switch (target->getTargetFileType())
  253. {
  254. case ProjectType::Target::TargetFileType::executable:
  255. functionName = "add_executable";
  256. break;
  257. case ProjectType::Target::TargetFileType::staticLibrary:
  258. case ProjectType::Target::TargetFileType::sharedLibraryOrDLL:
  259. case ProjectType::Target::TargetFileType::pluginBundle:
  260. functionName = "add_library";
  261. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::staticLibrary)
  262. properties.add ("STATIC");
  263. else if (target->getTargetFileType() == ProjectType::Target::TargetFileType::sharedLibraryOrDLL)
  264. properties.add ("SHARED");
  265. else
  266. properties.add ("MODULE");
  267. break;
  268. default:
  269. continue;
  270. }
  271. out << functionName << " (" << getTargetVarName (*target);
  272. if (! properties.isEmpty())
  273. out << " " << properties.joinIntoString (" ");
  274. out << newLine;
  275. std::vector<std::pair<String, bool>> fileInfoList;
  276. for (auto& group : exporter.getAllGroups())
  277. getFileInfoList (*target, exporter, group, fileInfoList);
  278. for (auto& fileInfo : fileInfoList)
  279. out << " " << fileInfo.first.quoted() << newLine;
  280. out << ")" << newLine << newLine;
  281. for (auto& fileInfo : fileInfoList)
  282. if (! fileInfo.second)
  283. out << "set_source_files_properties (" << fileInfo.first.quoted() << " PROPERTIES HEADER_FILE_ONLY TRUE)" << newLine;
  284. out << newLine;
  285. }
  286. }
  287. //==============================================================================
  288. void writeCMakeListsMakefileSection (OutputStream& out, MakefileProjectExporter& exporter) const
  289. {
  290. out << "project (" << getProject().getTitle() << " C CXX)" << newLine
  291. << newLine;
  292. out << "find_package (PkgConfig REQUIRED)" << newLine;
  293. StringArray cmakePkgconfigPackages;
  294. for (auto& package : exporter.getPackages())
  295. {
  296. cmakePkgconfigPackages.add (package.toUpperCase());
  297. out << "pkg_search_module (" << cmakePkgconfigPackages.strings.getLast() << " REQUIRED " << package << ")" << newLine;
  298. }
  299. out << newLine;
  300. writeCMakeTargets (out, exporter);
  301. for (auto* target : exporter.targets)
  302. {
  303. if (target->type == ProjectType::Target::Type::AggregateTarget)
  304. continue;
  305. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle)
  306. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES PREFIX \"\")" << newLine;
  307. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES SUFFIX \"" << target->getTargetFileSuffix() << "\")" << newLine
  308. << newLine;
  309. }
  310. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  311. {
  312. auto& config = dynamic_cast<const MakefileProjectExporter::MakeBuildConfiguration&> (*c);
  313. out << "#------------------------------------------------------------------------------" << newLine
  314. << "# Config: " << config.getName() << newLine
  315. << "#------------------------------------------------------------------------------" << newLine
  316. << newLine;
  317. const auto buildTypeCondition = String ("CMAKE_BUILD_TYPE STREQUAL " + config.getName());
  318. out << "if (" << buildTypeCondition << ")" << newLine
  319. << newLine;
  320. out << "execute_process (COMMAND uname -m OUTPUT_VARIABLE JUCE_ARCH_LABEL OUTPUT_STRIP_TRAILING_WHITESPACE)" << newLine
  321. << newLine;
  322. out << "include_directories (" << newLine;
  323. for (auto& path : exporter.getHeaderSearchPaths (config))
  324. out << " " << path.quoted() << newLine;
  325. for (auto& package : cmakePkgconfigPackages)
  326. out << " ${" << package << "_INCLUDE_DIRS}" << newLine;
  327. out << ")" << newLine << newLine;
  328. StringArray cmakeFoundLibraries;
  329. for (auto& library : exporter.getLibraryNames (config))
  330. {
  331. const String cmakeLibraryID (library.toUpperCase());
  332. cmakeFoundLibraries.add (String ("${") + cmakeLibraryID + "}");
  333. out << "find_library (" << cmakeLibraryID << " " << library << newLine;
  334. for (auto& path : exporter.getLibrarySearchPaths (config))
  335. out << " " << path.quoted() << newLine;
  336. out << ")" << newLine
  337. << newLine;
  338. }
  339. for (auto* target : exporter.targets)
  340. {
  341. if (target->type == ProjectType::Target::Type::AggregateTarget)
  342. continue;
  343. const auto targetVarName = getTargetVarName (*target);
  344. out << "set_target_properties (" << targetVarName << " PROPERTIES" << newLine
  345. << " OUTPUT_NAME " << config.getTargetBinaryNameString().quoted() << newLine
  346. << ")" << newLine << newLine;
  347. auto defines = exporter.getDefines (config);
  348. defines.addArray (target->getDefines (config));
  349. out << "target_compile_definitions (" << targetVarName << " PRIVATE" << newLine;
  350. for (auto& key : defines.getAllKeys())
  351. out << " " << key << "=" << defines[key] << newLine;
  352. out << ")" << newLine << newLine;
  353. auto targetFlags = target->getCompilerFlags();
  354. if (! targetFlags.isEmpty())
  355. {
  356. out << "target_compile_options (" << targetVarName << " PRIVATE" << newLine;
  357. for (auto& flag : targetFlags)
  358. out << " " << flag << newLine;
  359. out << ")" << newLine << newLine;
  360. }
  361. out << "target_link_libraries (" << targetVarName << " PRIVATE" << newLine;
  362. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  363. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  364. out << " SHARED_CODE" << newLine;
  365. out << " " << exporter.getArchFlags (config) << newLine;
  366. for (auto& flag : target->getLinkerFlags())
  367. out << " " << flag << newLine;
  368. for (auto& flag : exporter.getLinkerFlags (config))
  369. out << " " << flag << newLine;
  370. for (auto& lib : cmakeFoundLibraries)
  371. out << " " << lib << newLine;
  372. for (auto& package : cmakePkgconfigPackages)
  373. out << " ${" << package << "_LIBRARIES}" << newLine;
  374. out << ")" << newLine << newLine;
  375. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  376. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  377. out << "add_dependencies (" << targetVarName << " " << "SHARED_CODE)" << newLine << newLine;
  378. }
  379. StringArray cFlags;
  380. cFlags.add (exporter.getArchFlags (config));
  381. cFlags.addArray (exporter.getCPreprocessorFlags (config));
  382. cFlags.addArray (exporter.getCFlags (config));
  383. out << addToCMakeVariable ("CMAKE_C_FLAGS", cFlags.joinIntoString (" ")) << newLine
  384. << addToCMakeVariable ("CMAKE_CXX_FLAGS", "${CMAKE_C_FLAGS} " + exporter.getCXXFlags().joinIntoString (" ")) << newLine
  385. << newLine;
  386. out << "endif (" << buildTypeCondition << ")" << newLine
  387. << newLine;
  388. }
  389. }
  390. //==============================================================================
  391. void writeCMakeListsCodeBlocksSection (OutputStream& out, CodeBlocksProjectExporter& exporter) const
  392. {
  393. out << "project (" << getProject().getTitle() << " C CXX)" << newLine
  394. << newLine;
  395. writeCMakeTargets (out, exporter);
  396. for (auto* target : exporter.targets)
  397. {
  398. if (target->type == ProjectType::Target::Type::AggregateTarget)
  399. continue;
  400. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES PREFIX \"\")" << newLine
  401. << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES SUFFIX " << target->getTargetSuffix().quoted() << ")" << newLine
  402. << newLine;
  403. }
  404. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  405. {
  406. auto& config = dynamic_cast<const CodeBlocksProjectExporter::CodeBlocksBuildConfiguration&> (*c);
  407. out << "#------------------------------------------------------------------------------" << newLine
  408. << "# Config: " << config.getName() << newLine
  409. << "#------------------------------------------------------------------------------" << newLine
  410. << newLine;
  411. const auto buildTypeCondition = String ("CMAKE_BUILD_TYPE STREQUAL " + config.getName());
  412. out << "if (" << buildTypeCondition << ")" << newLine
  413. << newLine;
  414. out << "include_directories (" << newLine;
  415. for (auto& path : exporter.getIncludePaths (config))
  416. out << " " << path.quoted() << newLine;
  417. out << ")" << newLine << newLine;
  418. for (auto* target : exporter.targets)
  419. {
  420. if (target->type == ProjectType::Target::Type::AggregateTarget)
  421. continue;
  422. const auto targetVarName = getTargetVarName (*target);
  423. out << "set_target_properties (" << targetVarName << " PROPERTIES" << newLine
  424. << " OUTPUT_NAME " << config.getTargetBinaryNameString().quoted() << newLine
  425. << ")" << newLine << newLine;
  426. out << "target_compile_definitions (" << targetVarName << " PRIVATE" << newLine;
  427. for (auto& def : exporter.getDefines (config, *target))
  428. out << " " << def << newLine;
  429. out << ")" << newLine << newLine;
  430. out << "target_compile_options (" << targetVarName << " PRIVATE" << newLine;
  431. for (auto& option : exporter.getCompilerFlags (config, *target))
  432. out << " " << option.quoted() << newLine;
  433. out << ")" << newLine << newLine;
  434. out << "target_link_libraries (" << targetVarName << " PRIVATE" << newLine;
  435. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  436. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  437. out << " SHARED_CODE" << newLine
  438. << " -L." << newLine;
  439. for (auto& path : exporter.getLinkerSearchPaths (config, *target))
  440. {
  441. out << " \"-L";
  442. if (! File::isAbsolutePath (path))
  443. out << "${CMAKE_CURRENT_SOURCE_DIR}/";
  444. out << path << "\"" << newLine;
  445. }
  446. for (auto& flag : exporter.getLinkerFlags (config, *target))
  447. out << " " << flag << newLine;
  448. for (auto& flag : exporter.getProjectLinkerLibs())
  449. out << " -l" << flag << newLine;
  450. for (auto& lib : exporter.mingwLibs)
  451. out << " -l" << lib << newLine;
  452. out << ")" << newLine << newLine;
  453. }
  454. out << addToCMakeVariable ("CMAKE_CXX_FLAGS", exporter.getProjectCompilerOptions().joinIntoString (" ")) << newLine
  455. << addToCMakeVariable ("CMAKE_C_FLAGS", "${CMAKE_CXX_FLAGS}") << newLine
  456. << newLine;
  457. out << "endif (" << buildTypeCondition << ")" << newLine
  458. << newLine;
  459. }
  460. }
  461. //==============================================================================
  462. void writeCMakeListsXcodeSection (OutputStream& out, XcodeProjectExporter& exporter) const
  463. {
  464. // We need to dind out the SDK root before defining the project. Unfortunately this is
  465. // set per-target in the Xcode project, but we want it per-configuration.
  466. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  467. {
  468. auto& config = dynamic_cast<const XcodeProjectExporter::XcodeBuildConfiguration&> (*c);
  469. for (auto* target : exporter.targets)
  470. {
  471. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::macOSAppex
  472. || target->type == ProjectType::Target::Type::AggregateTarget
  473. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  474. continue;
  475. const auto targetAttributes = target->getTargetSettings (config);
  476. const auto targetAttributeKeys = targetAttributes.getAllKeys();
  477. if (targetAttributes.getAllKeys().contains ("SDKROOT"))
  478. {
  479. out << "if (CMAKE_BUILD_TYPE STREQUAL " + config.getName() << ")" << newLine
  480. << " set (CMAKE_OSX_SYSROOT " << targetAttributes["SDKROOT"] << ")" << newLine
  481. << "endif()" << newLine << newLine;
  482. break;
  483. }
  484. }
  485. }
  486. out << "project (" << getProject().getTitle() << " C CXX)" << newLine << newLine;
  487. writeCMakeTargets (out, exporter);
  488. for (auto* target : exporter.targets)
  489. {
  490. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::macOSAppex
  491. || target->type == ProjectType::Target::Type::AggregateTarget
  492. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  493. continue;
  494. if (target->type == ProjectType::Target::Type::AudioUnitPlugIn)
  495. out << "find_program (RC_COMPILER Rez NO_DEFAULT_PATHS PATHS /Applications/Xcode.app/Contents/Developer/usr/bin)" << newLine
  496. << "if (NOT RC_COMPILER)" << newLine
  497. << " message (WARNING \"failed to find Rez; older resource-based AU plug-ins may not work correctly\")" << newLine
  498. << "endif (NOT RC_COMPILER)" << newLine << newLine;
  499. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::staticLibrary
  500. || target->getTargetFileType() == ProjectType::Target::TargetFileType::sharedLibraryOrDLL)
  501. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES SUFFIX \"" << target->xcodeBundleExtension << "\")" << newLine
  502. << newLine;
  503. }
  504. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  505. {
  506. auto& config = dynamic_cast<const XcodeProjectExporter::XcodeBuildConfiguration&> (*c);
  507. out << "#------------------------------------------------------------------------------" << newLine
  508. << "# Config: " << config.getName() << newLine
  509. << "#------------------------------------------------------------------------------" << newLine
  510. << newLine;
  511. const auto buildTypeCondition = String ("CMAKE_BUILD_TYPE STREQUAL " + config.getName());
  512. out << "if (" << buildTypeCondition << ")" << newLine
  513. << newLine;
  514. out << "execute_process (COMMAND uname -m OUTPUT_VARIABLE JUCE_ARCH_LABEL OUTPUT_STRIP_TRAILING_WHITESPACE)" << newLine
  515. << newLine;
  516. const auto configSettings = exporter.getProjectSettings (config);
  517. auto configSettingsKeys = configSettings.getAllKeys();
  518. auto binaryName = config.getTargetBinaryNameString();
  519. if (configSettingsKeys.contains ("PRODUCT_NAME"))
  520. binaryName = configSettings["PRODUCT_NAME"].unquoted();
  521. for (auto* target : exporter.targets)
  522. {
  523. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::macOSAppex
  524. || target->type == ProjectType::Target::Type::AggregateTarget
  525. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  526. continue;
  527. const auto targetVarName = getTargetVarName (*target);
  528. auto targetAttributes = target->getTargetSettings (config);
  529. auto targetAttributeKeys = targetAttributes.getAllKeys();
  530. StringArray headerSearchPaths;
  531. if (targetAttributeKeys.contains ("HEADER_SEARCH_PATHS"))
  532. {
  533. auto paths = targetAttributes["HEADER_SEARCH_PATHS"].trim().substring (1).dropLastCharacters (1);
  534. paths = paths.replace ("\"$(inherited)\"", {});
  535. paths = paths.replace ("$(HOME)", "$ENV{HOME}");
  536. headerSearchPaths.addTokens (paths, ",\"\t\\", {});
  537. headerSearchPaths.removeEmptyStrings();
  538. targetAttributeKeys.removeString ("HEADER_SEARCH_PATHS");
  539. }
  540. out << "target_include_directories (" << targetVarName << " PRIVATE" << newLine;
  541. for (auto& path : headerSearchPaths)
  542. out << " " << path.quoted() << newLine;
  543. out << ")" << newLine << newLine;
  544. StringArray defines;
  545. if (targetAttributeKeys.contains ("GCC_PREPROCESSOR_DEFINITIONS"))
  546. {
  547. defines.addTokens (targetAttributes["GCC_PREPROCESSOR_DEFINITIONS"], "() ,\t", {});
  548. defines.removeEmptyStrings();
  549. targetAttributeKeys.removeString ("GCC_PREPROCESSOR_DEFINITIONS");
  550. }
  551. out << "target_compile_definitions (" << targetVarName << " PRIVATE" << newLine;
  552. for (auto& def : defines)
  553. out << " " << def << newLine;
  554. out << ")" << newLine << newLine;
  555. StringArray cppFlags;
  556. String archLabel ("${JUCE_ARCH_LABEL}");
  557. // Fat binaries are not supported.
  558. if (targetAttributeKeys.contains ("ARCHS"))
  559. {
  560. auto value = targetAttributes["ARCHS"].unquoted();
  561. if (value.contains ("NATIVE_ARCH_ACTUAL"))
  562. {
  563. cppFlags.add ("-march=native");
  564. }
  565. else if (value.contains ("ARCHS_STANDARD_32_BIT"))
  566. {
  567. archLabel = "i386";
  568. cppFlags.add ("-arch x86");
  569. }
  570. else if (value.contains ("ARCHS_STANDARD_32_64_BIT")
  571. || value.contains ("ARCHS_STANDARD_64_BIT"))
  572. {
  573. archLabel = "x86_64";
  574. cppFlags.add ("-arch x86_64");
  575. }
  576. targetAttributeKeys.removeString ("ARCHS");
  577. }
  578. if (targetAttributeKeys.contains ("MACOSX_DEPLOYMENT_TARGET"))
  579. {
  580. cppFlags.add ("-mmacosx-version-min=" + targetAttributes["MACOSX_DEPLOYMENT_TARGET"]);
  581. targetAttributeKeys.removeString ("MACOSX_DEPLOYMENT_TARGET");
  582. }
  583. if (targetAttributeKeys.contains ("OTHER_CPLUSPLUSFLAGS"))
  584. {
  585. cppFlags.add (targetAttributes["OTHER_CPLUSPLUSFLAGS"].unquoted());
  586. targetAttributeKeys.removeString ("OTHER_CPLUSPLUSFLAGS");
  587. }
  588. if (targetAttributeKeys.contains ("GCC_OPTIMIZATION_LEVEL"))
  589. {
  590. cppFlags.add ("-O" + targetAttributes["GCC_OPTIMIZATION_LEVEL"]);
  591. targetAttributeKeys.removeString ("GCC_OPTIMIZATION_LEVEL");
  592. }
  593. if (targetAttributeKeys.contains ("LLVM_LTO"))
  594. {
  595. cppFlags.add ("-flto");
  596. targetAttributeKeys.removeString ("LLVM_LTO");
  597. }
  598. if (targetAttributeKeys.contains ("GCC_FAST_MATH"))
  599. {
  600. cppFlags.add ("-ffast-math");
  601. targetAttributeKeys.removeString ("GCC_FAST_MATH");
  602. }
  603. if (targetAttributeKeys.contains ("CLANG_CXX_LANGUAGE_STANDARD"))
  604. {
  605. cppFlags.add ("-std=" + targetAttributes["CLANG_CXX_LANGUAGE_STANDARD"].unquoted());
  606. targetAttributeKeys.removeString ("CLANG_CXX_LANGUAGE_STANDARD");
  607. }
  608. if (targetAttributeKeys.contains ("CLANG_CXX_LIBRARY"))
  609. {
  610. cppFlags.add ("-stdlib=" + targetAttributes["CLANG_CXX_LIBRARY"].unquoted());
  611. targetAttributeKeys.removeString ("CLANG_CXX_LIBRARY");
  612. }
  613. out << "target_compile_options (" << targetVarName << " PRIVATE" << newLine;
  614. for (auto& flag : cppFlags)
  615. out << " " << flag << newLine;
  616. out << ")" << newLine << newLine;
  617. StringArray libSearchPaths;
  618. if (targetAttributeKeys.contains ("LIBRARY_SEARCH_PATHS"))
  619. {
  620. auto paths = targetAttributes["LIBRARY_SEARCH_PATHS"].trim().substring (1).dropLastCharacters (1);
  621. paths = paths.replace ("\"$(inherited)\"", {});
  622. paths = paths.replace ("$(HOME)", "$ENV{HOME}");
  623. libSearchPaths.addTokens (paths, ",\"\t\\", {});
  624. libSearchPaths.removeEmptyStrings();
  625. for (auto& libPath : libSearchPaths)
  626. {
  627. libPath = libPath.replace ("${CURRENT_ARCH}", archLabel);
  628. if (! File::isAbsolutePath (libPath))
  629. libPath = "${CMAKE_CURRENT_SOURCE_DIR}/" + libPath;
  630. }
  631. targetAttributeKeys.removeString ("LIBRARY_SEARCH_PATHS");
  632. }
  633. StringArray linkerFlags;
  634. if (targetAttributeKeys.contains ("OTHER_LDFLAGS"))
  635. {
  636. // CMake adds its own SHARED_CODE library linking flags
  637. auto flagsWithReplacedSpaces = targetAttributes["OTHER_LDFLAGS"].unquoted().replace ("\\\\ ", "^^%%^^");
  638. linkerFlags.addTokens (flagsWithReplacedSpaces, true);
  639. linkerFlags.removeString ("-bundle");
  640. linkerFlags.removeString ("-l" + binaryName.replace (" ", "^^%%^^"));
  641. for (auto& flag : linkerFlags)
  642. flag = flag.replace ("^^%%^^", " ");
  643. targetAttributeKeys.removeString ("OTHER_LDFLAGS");
  644. }
  645. if (target->type == ProjectType::Target::Type::AudioUnitPlugIn)
  646. {
  647. String rezFlags;
  648. if (targetAttributeKeys.contains ("OTHER_REZFLAGS"))
  649. {
  650. rezFlags = targetAttributes["OTHER_REZFLAGS"];
  651. targetAttributeKeys.removeString ("OTHER_REZFLAGS");
  652. }
  653. for (auto& item : exporter.getAllGroups())
  654. {
  655. if (item.getName() == "Juce Library Code")
  656. {
  657. String resSourcesVar (targetVarName + "_REZ_SOURCES");
  658. String resOutputVar (targetVarName + "_REZ_OUTPUT");
  659. out << "if (RC_COMPILER)" << newLine
  660. << " set (" << resSourcesVar << newLine
  661. << " \"" << item.determineGroupFolder().getFullPathName() + "/include_juce_audio_plugin_client_AU.r\"" << newLine
  662. << " )" << newLine
  663. << " set (" << resOutputVar << " \"${CMAKE_CURRENT_BINARY_DIR}/" << binaryName << ".rsrc\")" << newLine
  664. << " target_sources (" << targetVarName << " PRIVATE" << newLine
  665. << " ${" << resSourcesVar << "}" << newLine
  666. << " ${" << resOutputVar << "}" << newLine
  667. << " )" << newLine
  668. << " execute_process (COMMAND" << newLine
  669. << " ${RC_COMPILER}" << newLine
  670. << " " << rezFlags.unquoted().removeCharacters ("\\") << newLine;
  671. for (auto& path : headerSearchPaths)
  672. out << " -I \"${PROJECT_SOURCE_DIR}/" << path.unquoted() << "\"" << newLine;
  673. out << " ${" << resSourcesVar << "}" << newLine
  674. << " -o ${" << resOutputVar << "}" << newLine
  675. << " )" << newLine
  676. << " set_source_files_properties (${" << resOutputVar << "} PROPERTIES" << newLine
  677. << " GENERATED TRUE" << newLine
  678. << " MACOSX_PACKAGE_LOCATION Resources" << newLine
  679. << " )" << newLine
  680. << "endif (RC_COMPILER)" << newLine << newLine;
  681. break;
  682. }
  683. }
  684. }
  685. if (targetAttributeKeys.contains ("INFOPLIST_FILE"))
  686. {
  687. auto plistFile = exporter.getTargetFolder().getChildFile (targetAttributes["INFOPLIST_FILE"]);
  688. XmlDocument infoPlistData (plistFile);
  689. ScopedPointer<XmlElement> plist = infoPlistData.getDocumentElement();
  690. if (plist != nullptr)
  691. {
  692. if (auto* dict = plist->getChildByName ("dict"))
  693. {
  694. if (auto* entry = dict->getChildByName ("key"))
  695. {
  696. while (entry != nullptr)
  697. {
  698. if (entry->getAllSubText() == "CFBundleExecutable")
  699. {
  700. if (auto* bundleName = entry->getNextElementWithTagName ("string"))
  701. {
  702. bundleName->deleteAllTextElements();
  703. bundleName->addTextElement (binaryName);
  704. }
  705. }
  706. entry = entry->getNextElementWithTagName ("key");
  707. }
  708. }
  709. }
  710. File updatedPlist (getTargetFolder().getChildFile (config.getName() + "-" + plistFile.getFileName()));
  711. plist->writeToFile (updatedPlist, "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
  712. targetAttributes.set ("INFOPLIST_FILE", updatedPlist.getFullPathName().quoted());
  713. }
  714. else
  715. {
  716. targetAttributeKeys.removeString ("INFOPLIST_FILE");
  717. }
  718. }
  719. targetAttributeKeys.sort (false);
  720. out << "set_target_properties (" << targetVarName << " PROPERTIES" << newLine
  721. << " OUTPUT_NAME " << binaryName.quoted() << newLine;
  722. for (auto& key : targetAttributeKeys)
  723. out << " XCODE_ATTRIBUTE_" << key << " " << targetAttributes[key] << newLine;
  724. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::executable
  725. || target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle)
  726. {
  727. out << " MACOSX_BUNDLE_INFO_PLIST " << targetAttributes.getValue ("INFOPLIST_FILE", "\"\"") << newLine
  728. << " XCODE_ATTRIBUTE_PRODUCT_NAME " << binaryName.quoted() << newLine;
  729. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::executable)
  730. {
  731. out << " MACOSX_BUNDLE TRUE" << newLine;
  732. }
  733. else
  734. {
  735. out << " BUNDLE TRUE" << newLine
  736. << " BUNDLE_EXTENSION " << targetAttributes.getValue ("WRAPPER_EXTENSION", "\"\"") << newLine
  737. << " XCODE_ATTRIBUTE_MACH_O_TYPE \"mh_bundle\"" << newLine;
  738. }
  739. }
  740. out << ")" << newLine << newLine;
  741. out << "target_link_libraries (" << targetVarName << " PRIVATE" << newLine;
  742. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  743. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  744. out << " SHARED_CODE" << newLine;
  745. for (auto& path : libSearchPaths)
  746. out << " \"-L" << path << "\"" << newLine;
  747. for (auto& flag : linkerFlags)
  748. out << " " << flag.quoted() << newLine;
  749. for (auto& framework : target->frameworkNames)
  750. out << " \"-framework " << framework << "\"" << newLine;
  751. out << ")" << newLine << newLine;
  752. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  753. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  754. {
  755. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  756. && targetAttributeKeys.contains("INSTALL_PATH"))
  757. {
  758. const auto installPath = targetAttributes["INSTALL_PATH"].unquoted();
  759. const auto productFilename = binaryName + (targetAttributeKeys.contains ("WRAPPER_EXTENSION") ? "." + targetAttributes["WRAPPER_EXTENSION"] : String());
  760. auto productPath = (installPath + productFilename).quoted();
  761. out << "add_custom_command (TARGET " << targetVarName << " POST_BUILD" << newLine
  762. << " COMMAND cmake -E remove_directory " << productPath << newLine
  763. << " COMMAND cmake -E copy_directory \"${CMAKE_BINARY_DIR}/" << productFilename << "\" " << productPath << newLine
  764. << " COMMENT \"Copying \\\"" << productFilename << "\\\" to \\\"" << installPath.unquoted() << "\\\"\"" << newLine
  765. << ")" << newLine << newLine;
  766. }
  767. }
  768. }
  769. std::map<String, String> basicWarnings
  770. {
  771. { "CLANG_WARN_BOOL_CONVERSION", "bool-conversion" },
  772. { "CLANG_WARN_COMMA", "comma" },
  773. { "CLANG_WARN_CONSTANT_CONVERSION", "constant-conversion" },
  774. { "CLANG_WARN_EMPTY_BODY", "empty-body" },
  775. { "CLANG_WARN_ENUM_CONVERSION", "enum-conversion" },
  776. { "CLANG_WARN_INFINITE_RECURSION", "infinite-recursion" },
  777. { "CLANG_WARN_INT_CONVERSION", "int-conversion" },
  778. { "CLANG_WARN_RANGE_LOOP_ANALYSIS", "range-loop-analysis" },
  779. { "CLANG_WARN_STRICT_PROTOTYPES", "strict-prototypes" },
  780. { "GCC_WARN_CHECK_SWITCH_STATEMENTS", "switch" },
  781. { "GCC_WARN_UNUSED_VARIABLE", "unused-variable" },
  782. { "GCC_WARN_MISSING_PARENTHESES", "parentheses" },
  783. { "GCC_WARN_NON_VIRTUAL_DESTRUCTOR", "non-virtual-dtor" },
  784. { "GCC_WARN_64_TO_32_BIT_CONVERSION", "shorten-64-to-32" },
  785. { "GCC_WARN_UNDECLARED_SELECTOR", "undeclared-selector" },
  786. { "GCC_WARN_UNUSED_FUNCTION", "unused-function" }
  787. };
  788. StringArray compilerFlags;
  789. for (auto& key : configSettingsKeys)
  790. {
  791. auto basicWarning = basicWarnings.find (key);
  792. if (basicWarning != basicWarnings.end())
  793. {
  794. compilerFlags.add (configSettings[key] == "YES" ? "-W" + basicWarning->second : "-Wno-" + basicWarning->second);
  795. }
  796. else if (key == "CLANG_WARN_SUSPICIOUS_MOVE" && configSettings[key] == "YES") compilerFlags.add ("-Wmove");
  797. else if (key == "CLANG_WARN_UNREACHABLE_CODE" && configSettings[key] == "YES") compilerFlags.add ("-Wunreachable-code");
  798. else if (key == "CLANG_WARN__DUPLICATE_METHOD_MATCH" && configSettings[key] == "YES") compilerFlags.add ("-Wduplicate-method-match");
  799. else if (key == "GCC_INLINES_ARE_PRIVATE_EXTERN" && configSettings[key] == "YES") compilerFlags.add ("-fvisibility-inlines-hidden");
  800. else if (key == "GCC_NO_COMMON_BLOCKS" && configSettings[key] == "YES") compilerFlags.add ("-fno-common");
  801. else if (key == "GCC_WARN_ABOUT_RETURN_TYPE" && configSettings[key] != "YES") compilerFlags.add (configSettings[key] == "YES_ERROR" ? "-Werror=return-type" : "-Wno-return-type");
  802. else if (key == "GCC_WARN_TYPECHECK_CALLS_TO_PRINTF" && configSettings[key] != "YES") compilerFlags.add ("-Wno-format");
  803. else if (key == "GCC_WARN_UNINITIALIZED_AUTOS")
  804. {
  805. if (configSettings[key] == "YES") compilerFlags.add ("-Wuninitialized");
  806. else if (configSettings[key] == "YES_AGGRESSIVE") compilerFlags.add ("--Wconditional-uninitialized");
  807. else compilerFlags.add (")-Wno-uninitialized");
  808. }
  809. else if (key == "WARNING_CFLAGS") compilerFlags.add (configSettings[key]);
  810. }
  811. out << addToCMakeVariable ("CMAKE_CXX_FLAGS", compilerFlags.joinIntoString (" ")) << newLine
  812. << addToCMakeVariable ("CMAKE_C_FLAGS", "${CMAKE_CXX_FLAGS}") << newLine
  813. << newLine;
  814. out << "endif (" << buildTypeCondition << ")" << newLine
  815. << newLine;
  816. }
  817. }
  818. //==============================================================================
  819. JUCE_DECLARE_NON_COPYABLE (CLionProjectExporter)
  820. };