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.

1108 lines
50KB

  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. String getModuleLibraryArchName() const override { return {}; }
  35. };
  36. BuildConfiguration::Ptr createBuildConfig (const ValueTree& tree) const override
  37. {
  38. return new CLionBuildConfiguration (project, tree, *this);
  39. }
  40. public:
  41. //==============================================================================
  42. static const char* getName() { return "CLion (beta)"; }
  43. static const char* getValueTreeTypeName() { return "CLION"; }
  44. static CLionProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  45. {
  46. if (settings.hasType (getValueTreeTypeName()))
  47. return new CLionProjectExporter (project, settings);
  48. return nullptr;
  49. }
  50. static bool isExporterSupported (const ProjectExporter& exporter)
  51. {
  52. return exporter.isMakefile()
  53. || (exporter.isXcode() && ! exporter.isiOS())
  54. || (exporter.isCodeBlocks() && exporter.isWindows());
  55. }
  56. //==============================================================================
  57. CLionProjectExporter (Project& p, const ValueTree& t) : ProjectExporter (p, t)
  58. {
  59. name = getName();
  60. targetLocationValue.setDefault (getDefaultBuildsRootFolder() + "CLion");
  61. }
  62. //==============================================================================
  63. bool usesMMFiles() const override { return false; }
  64. bool canCopeWithDuplicateFiles() override { return false; }
  65. bool supportsUserDefinedConfigurations() const override { return false; }
  66. bool isXcode() const override { return false; }
  67. bool isVisualStudio() const override { return false; }
  68. bool isCodeBlocks() const override { return false; }
  69. bool isMakefile() const override { return false; }
  70. bool isAndroidStudio() const override { return false; }
  71. bool isCLion() const override { return true; }
  72. bool isAndroid() const override { return false; }
  73. bool isWindows() const override { return false; }
  74. bool isLinux() const override { return false; }
  75. bool isOSX() const override { return false; }
  76. bool isiOS() const override { return false; }
  77. bool supportsTargetType (ProjectType::Target::Type) const override { return true; }
  78. void addPlatformSpecificSettingsForProjectType (const ProjectType&) override {}
  79. void initialiseDependencyPathValues() override {}
  80. //==============================================================================
  81. bool canLaunchProject() override
  82. {
  83. #if JUCE_MAC
  84. static Identifier exporterName ("XCODE_MAC");
  85. #elif JUCE_WINDOWS
  86. static Identifier exporterName ("CODEBLOCKS_WINDOWS");
  87. #elif JUCE_LINUX
  88. static Identifier exporterName ("LINUX_MAKE");
  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().quoted());
  99. }
  100. String getDescription() override
  101. {
  102. String description;
  103. description << "The " << getName() << " exporter produces a single CMakeLists.txt file with "
  104. << "multiple platform dependent 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 the CLion exporter 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. File clionExe (getAppSettings()
  183. .getStoredPath (Ids::clionExePath)
  184. .toString()
  185. .replace ("${user.home}", File::getSpecialLocation (File::userHomeDirectory).getFullPathName()));
  186. #if JUCE_MAC
  187. if (clionExe.getFileName().endsWith (".app"))
  188. clionExe = clionExe.getChildFile ("Contents/MacOS/clion");
  189. #endif
  190. return clionExe;
  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 bool isWindowsAbsolutePath (const String& path)
  215. {
  216. return path.length() > 1 && path[1] == ':';
  217. }
  218. static bool isUnixAbsolutePath (const String& path)
  219. {
  220. return path.isNotEmpty() && (path[0] == '/' || path[0] == '~' || path.startsWith ("$ENV{HOME}"));
  221. }
  222. //==============================================================================
  223. static String setCMakeVariable (const String& variableName, const String& value)
  224. {
  225. return String ("set (") + variableName + " \"" + value + "\")";
  226. }
  227. static String addToCMakeVariable (const String& variableName, const String& value)
  228. {
  229. return setCMakeVariable (variableName, "${" + variableName + "} " + value);
  230. }
  231. static String getTargetVarName (ProjectType::Target& target)
  232. {
  233. return String (target.getName()).toUpperCase().replaceCharacter (L' ', L'_');
  234. }
  235. template <class Target, class Exporter>
  236. void getFileInfoList (Target& target, Exporter& exporter, const Project::Item& projectItem, std::vector<std::pair<String, bool>>& fileInfoList) const
  237. {
  238. const auto targetType = (getProject().getProjectType().isAudioPlugin() ? target.type : Target::Type::SharedCodeTarget);
  239. if (projectItem.isGroup())
  240. {
  241. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  242. getFileInfoList (target, exporter, projectItem.getChild(i), fileInfoList);
  243. }
  244. else if (projectItem.shouldBeAddedToTargetProject() && getProject().getTargetTypeFromFilePath (projectItem.getFile(), true) == targetType )
  245. {
  246. const auto path = RelativePath (projectItem.getFile(), exporter.getTargetFolder(), RelativePath::buildTargetFolder).toUnixStyle();
  247. fileInfoList.push_back ({ path, projectItem.shouldBeCompiled() });
  248. }
  249. }
  250. template <class Exporter>
  251. void writeCMakeTargets (OutputStream& out, Exporter& exporter) const
  252. {
  253. for (auto* target : exporter.targets)
  254. {
  255. if (target->type == ProjectType::Target::Type::AggregateTarget
  256. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  257. continue;
  258. String functionName;
  259. StringArray properties;
  260. switch (target->getTargetFileType())
  261. {
  262. case ProjectType::Target::TargetFileType::executable:
  263. functionName = "add_executable";
  264. if (exporter.isCodeBlocks() && exporter.isWindows()
  265. && target->type != ProjectType::Target::Type::ConsoleApp)
  266. properties.add ("WIN32");
  267. break;
  268. case ProjectType::Target::TargetFileType::staticLibrary:
  269. case ProjectType::Target::TargetFileType::sharedLibraryOrDLL:
  270. case ProjectType::Target::TargetFileType::pluginBundle:
  271. functionName = "add_library";
  272. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::staticLibrary)
  273. properties.add ("STATIC");
  274. else if (target->getTargetFileType() == ProjectType::Target::TargetFileType::sharedLibraryOrDLL)
  275. properties.add ("SHARED");
  276. else
  277. properties.add ("MODULE");
  278. break;
  279. default:
  280. continue;
  281. }
  282. out << functionName << " (" << getTargetVarName (*target);
  283. if (! properties.isEmpty())
  284. out << " " << properties.joinIntoString (" ");
  285. out << newLine;
  286. std::vector<std::pair<String, bool>> fileInfoList;
  287. for (auto& group : exporter.getAllGroups())
  288. getFileInfoList (*target, exporter, group, fileInfoList);
  289. for (auto& fileInfo : fileInfoList)
  290. out << " " << fileInfo.first.quoted() << newLine;
  291. out << ")" << newLine << newLine;
  292. for (auto& fileInfo : fileInfoList)
  293. if (! fileInfo.second)
  294. out << "set_source_files_properties (" << fileInfo.first.quoted() << " PROPERTIES HEADER_FILE_ONLY TRUE)" << newLine;
  295. out << newLine;
  296. }
  297. }
  298. //==============================================================================
  299. void writeCMakeListsMakefileSection (OutputStream& out, MakefileProjectExporter& exporter) const
  300. {
  301. out << "project (" << getProject().getProjectNameString().quoted() << " C CXX)" << newLine
  302. << newLine;
  303. out << "find_package (PkgConfig REQUIRED)" << newLine;
  304. StringArray cmakePkgconfigPackages;
  305. for (auto& package : exporter.getPackages())
  306. {
  307. cmakePkgconfigPackages.add (package.toUpperCase());
  308. out << "pkg_search_module (" << cmakePkgconfigPackages.strings.getLast() << " REQUIRED " << package << ")" << newLine;
  309. }
  310. out << newLine;
  311. writeCMakeTargets (out, exporter);
  312. for (auto* target : exporter.targets)
  313. {
  314. if (target->type == ProjectType::Target::Type::AggregateTarget)
  315. continue;
  316. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle)
  317. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES PREFIX \"\")" << newLine;
  318. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES SUFFIX \"" << target->getTargetFileSuffix() << "\")" << newLine
  319. << newLine;
  320. }
  321. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  322. {
  323. auto& config = dynamic_cast<const MakefileProjectExporter::MakeBuildConfiguration&> (*c);
  324. out << "#------------------------------------------------------------------------------" << newLine
  325. << "# Config: " << config.getName() << newLine
  326. << "#------------------------------------------------------------------------------" << newLine
  327. << newLine;
  328. const auto buildTypeCondition = String ("CMAKE_BUILD_TYPE STREQUAL " + config.getName());
  329. out << "if (" << buildTypeCondition << ")" << newLine
  330. << newLine;
  331. out << "execute_process (COMMAND uname -m OUTPUT_VARIABLE JUCE_ARCH_LABEL OUTPUT_STRIP_TRAILING_WHITESPACE)" << newLine
  332. << newLine;
  333. out << "include_directories (" << newLine;
  334. for (auto& path : exporter.getHeaderSearchPaths (config))
  335. out << " " << path.quoted() << newLine;
  336. for (auto& package : cmakePkgconfigPackages)
  337. out << " ${" << package << "_INCLUDE_DIRS}" << newLine;
  338. out << ")" << newLine << newLine;
  339. StringArray cmakeFoundLibraries;
  340. for (auto& library : exporter.getLibraryNames (config))
  341. {
  342. const String cmakeLibraryID (library.toUpperCase());
  343. cmakeFoundLibraries.add (String ("${") + cmakeLibraryID + "}");
  344. out << "find_library (" << cmakeLibraryID << " " << library << newLine;
  345. for (auto& path : exporter.getLibrarySearchPaths (config))
  346. out << " " << path.quoted() << newLine;
  347. out << ")" << newLine
  348. << newLine;
  349. }
  350. for (auto* target : exporter.targets)
  351. {
  352. if (target->type == ProjectType::Target::Type::AggregateTarget)
  353. continue;
  354. const auto targetVarName = getTargetVarName (*target);
  355. out << "set_target_properties (" << targetVarName << " PROPERTIES" << newLine
  356. << " OUTPUT_NAME " << config.getTargetBinaryNameString().quoted() << newLine;
  357. auto cxxStandard = project.getCppStandardString();
  358. if (cxxStandard == "latest")
  359. cxxStandard = "1z";
  360. out << " CXX_STANDARD " << cxxStandard << newLine;
  361. if (! shouldUseGNUExtensions())
  362. out << " CXX_EXTENSIONS OFF" << newLine;
  363. out << ")" << newLine << newLine;
  364. auto defines = exporter.getDefines (config);
  365. defines.addArray (target->getDefines (config));
  366. out << "target_compile_definitions (" << targetVarName << " PRIVATE" << newLine;
  367. for (auto& key : defines.getAllKeys())
  368. out << " " << key << "=" << defines[key] << newLine;
  369. out << ")" << newLine << newLine;
  370. auto targetFlags = target->getCompilerFlags();
  371. if (! targetFlags.isEmpty())
  372. {
  373. out << "target_compile_options (" << targetVarName << " PRIVATE" << newLine;
  374. for (auto& flag : targetFlags)
  375. out << " " << flag << newLine;
  376. out << ")" << newLine << newLine;
  377. }
  378. out << "target_link_libraries (" << targetVarName << " PRIVATE" << newLine;
  379. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  380. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  381. out << " SHARED_CODE" << newLine;
  382. out << " " << exporter.getArchFlags (config) << newLine;
  383. for (auto& flag : target->getLinkerFlags())
  384. out << " " << flag << newLine;
  385. for (auto& flag : exporter.getLinkerFlags (config))
  386. out << " " << flag << newLine;
  387. for (auto& lib : cmakeFoundLibraries)
  388. out << " " << lib << newLine;
  389. for (auto& package : cmakePkgconfigPackages)
  390. out << " ${" << package << "_LIBRARIES}" << newLine;
  391. out << ")" << newLine << newLine;
  392. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  393. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  394. out << "add_dependencies (" << targetVarName << " " << "SHARED_CODE)" << newLine << newLine;
  395. }
  396. StringArray cFlags;
  397. cFlags.add (exporter.getArchFlags (config));
  398. cFlags.addArray (exporter.getCPreprocessorFlags (config));
  399. cFlags.addArray (exporter.getCFlags (config));
  400. out << addToCMakeVariable ("CMAKE_C_FLAGS", cFlags.joinIntoString (" ")) << newLine;
  401. String cxxFlags;
  402. for (auto& flag : exporter.getCXXFlags())
  403. if (! flag.startsWith ("-std="))
  404. cxxFlags += " " + flag;
  405. out << addToCMakeVariable ("CMAKE_CXX_FLAGS", "${CMAKE_C_FLAGS} " + cxxFlags) << newLine
  406. << newLine;
  407. out << "endif (" << buildTypeCondition << ")" << newLine
  408. << newLine;
  409. }
  410. }
  411. //==============================================================================
  412. void writeCMakeListsCodeBlocksSection (OutputStream& out, CodeBlocksProjectExporter& exporter) const
  413. {
  414. out << "project (" << getProject().getProjectNameString().quoted() << " C CXX)" << newLine
  415. << newLine;
  416. writeCMakeTargets (out, exporter);
  417. for (auto* target : exporter.targets)
  418. {
  419. if (target->type == ProjectType::Target::Type::AggregateTarget)
  420. continue;
  421. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES PREFIX \"\")" << newLine
  422. << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES SUFFIX " << target->getTargetSuffix().quoted() << ")" << newLine
  423. << newLine;
  424. }
  425. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  426. {
  427. auto& config = dynamic_cast<const CodeBlocksProjectExporter::CodeBlocksBuildConfiguration&> (*c);
  428. out << "#------------------------------------------------------------------------------" << newLine
  429. << "# Config: " << config.getName() << newLine
  430. << "#------------------------------------------------------------------------------" << newLine
  431. << newLine;
  432. const auto buildTypeCondition = String ("CMAKE_BUILD_TYPE STREQUAL " + config.getName());
  433. out << "if (" << buildTypeCondition << ")" << newLine
  434. << newLine;
  435. out << "include_directories (" << newLine;
  436. for (auto& path : exporter.getIncludePaths (config))
  437. out << " " << path.replace ("\\", "/").quoted() << newLine;
  438. out << ")" << newLine << newLine;
  439. for (auto* target : exporter.targets)
  440. {
  441. if (target->type == ProjectType::Target::Type::AggregateTarget)
  442. continue;
  443. const auto targetVarName = getTargetVarName (*target);
  444. out << "set_target_properties (" << targetVarName << " PROPERTIES" << newLine
  445. << " OUTPUT_NAME " << config.getTargetBinaryNameString().quoted() << newLine;
  446. auto cxxStandard = project.getCppStandardString();
  447. if (cxxStandard == "latest")
  448. cxxStandard = "1z";
  449. out << " CXX_STANDARD " << cxxStandard << newLine;
  450. if (! shouldUseGNUExtensions())
  451. out << " CXX_EXTENSIONS OFF" << newLine;
  452. out << ")" << newLine << newLine;
  453. out << "target_compile_definitions (" << targetVarName << " PRIVATE" << newLine;
  454. for (auto& def : exporter.getDefines (config, *target))
  455. out << " " << def << newLine;
  456. out << ")" << newLine << newLine;
  457. out << "target_compile_options (" << targetVarName << " PRIVATE" << newLine;
  458. for (auto& option : exporter.getCompilerFlags (config, *target))
  459. if (! option.startsWith ("-std="))
  460. out << " " << option.quoted() << newLine;
  461. out << ")" << newLine << newLine;
  462. out << "target_link_libraries (" << targetVarName << " PRIVATE" << newLine;
  463. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  464. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  465. out << " SHARED_CODE" << newLine
  466. << " -L." << newLine;
  467. for (auto& path : exporter.getLinkerSearchPaths (config, *target))
  468. {
  469. out << " \"-L\\\"";
  470. if (! isWindowsAbsolutePath (path))
  471. out << "${CMAKE_CURRENT_SOURCE_DIR}/";
  472. out << path.replace ("\\", "/") << "\\\"\"" << newLine;
  473. }
  474. for (auto& flag : exporter.getLinkerFlags (config, *target))
  475. out << " " << flag << newLine;
  476. for (auto& flag : exporter.getProjectLinkerLibs())
  477. out << " -l" << flag << newLine;
  478. for (auto& lib : exporter.mingwLibs)
  479. out << " -l" << lib << newLine;
  480. out << ")" << newLine << newLine;
  481. }
  482. out << addToCMakeVariable ("CMAKE_CXX_FLAGS", exporter.getProjectCompilerOptions().joinIntoString (" ")) << newLine
  483. << addToCMakeVariable ("CMAKE_C_FLAGS", "${CMAKE_CXX_FLAGS}") << newLine
  484. << newLine;
  485. out << "endif (" << buildTypeCondition << ")" << newLine
  486. << newLine;
  487. }
  488. }
  489. //==============================================================================
  490. void writeCMakeListsXcodeSection (OutputStream& out, XcodeProjectExporter& exporter) const
  491. {
  492. // We need to dind out the SDK root before defining the project. Unfortunately this is
  493. // set per-target in the Xcode project, but we want it per-configuration.
  494. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  495. {
  496. auto& config = dynamic_cast<const XcodeProjectExporter::XcodeBuildConfiguration&> (*c);
  497. for (auto* target : exporter.targets)
  498. {
  499. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::macOSAppex
  500. || target->type == ProjectType::Target::Type::AggregateTarget
  501. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  502. continue;
  503. const auto targetAttributes = target->getTargetSettings (config);
  504. const auto targetAttributeKeys = targetAttributes.getAllKeys();
  505. if (targetAttributes.getAllKeys().contains ("SDKROOT"))
  506. {
  507. out << "if (CMAKE_BUILD_TYPE STREQUAL " + config.getName() << ")" << newLine
  508. << " set (CMAKE_OSX_SYSROOT " << targetAttributes["SDKROOT"] << ")" << newLine
  509. << "endif()" << newLine << newLine;
  510. break;
  511. }
  512. }
  513. }
  514. out << "project (" << getProject().getProjectNameString().quoted() << " C CXX)" << newLine << newLine;
  515. writeCMakeTargets (out, exporter);
  516. for (auto* target : exporter.targets)
  517. {
  518. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::macOSAppex
  519. || target->type == ProjectType::Target::Type::AggregateTarget
  520. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  521. continue;
  522. if (target->type == ProjectType::Target::Type::AudioUnitPlugIn)
  523. out << "find_program (RC_COMPILER Rez NO_DEFAULT_PATHS PATHS /Applications/Xcode.app/Contents/Developer/usr/bin)" << newLine
  524. << "if (NOT RC_COMPILER)" << newLine
  525. << " message (WARNING \"failed to find Rez; older resource-based AU plug-ins may not work correctly\")" << newLine
  526. << "endif (NOT RC_COMPILER)" << newLine << newLine;
  527. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::staticLibrary
  528. || target->getTargetFileType() == ProjectType::Target::TargetFileType::sharedLibraryOrDLL)
  529. out << "set_target_properties (" << getTargetVarName (*target) << " PROPERTIES SUFFIX \"" << target->xcodeBundleExtension << "\")" << newLine
  530. << newLine;
  531. }
  532. for (ProjectExporter::ConstConfigIterator c (exporter); c.next();)
  533. {
  534. auto& config = dynamic_cast<const XcodeProjectExporter::XcodeBuildConfiguration&> (*c);
  535. out << "#------------------------------------------------------------------------------" << newLine
  536. << "# Config: " << config.getName() << newLine
  537. << "#------------------------------------------------------------------------------" << newLine
  538. << newLine;
  539. const auto buildTypeCondition = String ("CMAKE_BUILD_TYPE STREQUAL " + config.getName());
  540. out << "if (" << buildTypeCondition << ")" << newLine
  541. << newLine;
  542. out << "execute_process (COMMAND uname -m OUTPUT_VARIABLE JUCE_ARCH_LABEL OUTPUT_STRIP_TRAILING_WHITESPACE)" << newLine
  543. << newLine;
  544. const auto configSettings = exporter.getProjectSettings (config);
  545. auto configSettingsKeys = configSettings.getAllKeys();
  546. auto binaryName = config.getTargetBinaryNameString();
  547. if (configSettingsKeys.contains ("PRODUCT_NAME"))
  548. binaryName = configSettings["PRODUCT_NAME"].unquoted();
  549. for (auto* target : exporter.targets)
  550. {
  551. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::macOSAppex
  552. || target->type == ProjectType::Target::Type::AggregateTarget
  553. || target->type == ProjectType::Target::Type::AudioUnitv3PlugIn)
  554. continue;
  555. const auto targetVarName = getTargetVarName (*target);
  556. auto targetAttributes = target->getTargetSettings (config);
  557. auto targetAttributeKeys = targetAttributes.getAllKeys();
  558. StringArray headerSearchPaths;
  559. if (targetAttributeKeys.contains ("HEADER_SEARCH_PATHS"))
  560. {
  561. auto paths = targetAttributes["HEADER_SEARCH_PATHS"].trim().substring (1).dropLastCharacters (1);
  562. paths = paths.replace ("\"$(inherited)\"", {})
  563. .replace ("$(HOME)", "$ENV{HOME}")
  564. .replace ("~", "$ENV{HOME}");
  565. headerSearchPaths.addTokens (paths, ",\"\t\\", {});
  566. headerSearchPaths.removeEmptyStrings();
  567. targetAttributeKeys.removeString ("HEADER_SEARCH_PATHS");
  568. }
  569. out << "target_include_directories (" << targetVarName << " PRIVATE" << newLine;
  570. for (auto& path : headerSearchPaths)
  571. out << " " << path.quoted() << newLine;
  572. out << ")" << newLine << newLine;
  573. StringArray defines;
  574. if (targetAttributeKeys.contains ("GCC_PREPROCESSOR_DEFINITIONS"))
  575. {
  576. defines.addTokens (targetAttributes["GCC_PREPROCESSOR_DEFINITIONS"], "() ,\t", {});
  577. defines.removeEmptyStrings();
  578. targetAttributeKeys.removeString ("GCC_PREPROCESSOR_DEFINITIONS");
  579. }
  580. out << "target_compile_definitions (" << targetVarName << " PRIVATE" << newLine;
  581. for (auto& def : defines)
  582. out << " " << def << newLine;
  583. out << ")" << newLine << newLine;
  584. StringArray cppFlags;
  585. String archLabel ("${JUCE_ARCH_LABEL}");
  586. // Fat binaries are not supported.
  587. if (targetAttributeKeys.contains ("ARCHS"))
  588. {
  589. auto value = targetAttributes["ARCHS"].unquoted();
  590. if (value.contains ("NATIVE_ARCH_ACTUAL"))
  591. {
  592. cppFlags.add ("-march=native");
  593. }
  594. else if (value.contains ("ARCHS_STANDARD_32_BIT"))
  595. {
  596. archLabel = "i386";
  597. cppFlags.add ("-arch x86");
  598. }
  599. else if (value.contains ("ARCHS_STANDARD_32_64_BIT")
  600. || value.contains ("ARCHS_STANDARD_64_BIT"))
  601. {
  602. archLabel = "x86_64";
  603. cppFlags.add ("-arch x86_64");
  604. }
  605. targetAttributeKeys.removeString ("ARCHS");
  606. }
  607. if (targetAttributeKeys.contains ("MACOSX_DEPLOYMENT_TARGET"))
  608. {
  609. cppFlags.add ("-mmacosx-version-min=" + targetAttributes["MACOSX_DEPLOYMENT_TARGET"]);
  610. targetAttributeKeys.removeString ("MACOSX_DEPLOYMENT_TARGET");
  611. }
  612. if (targetAttributeKeys.contains ("OTHER_CPLUSPLUSFLAGS"))
  613. {
  614. cppFlags.add (targetAttributes["OTHER_CPLUSPLUSFLAGS"].unquoted());
  615. targetAttributeKeys.removeString ("OTHER_CPLUSPLUSFLAGS");
  616. }
  617. if (targetAttributeKeys.contains ("GCC_OPTIMIZATION_LEVEL"))
  618. {
  619. cppFlags.add ("-O" + targetAttributes["GCC_OPTIMIZATION_LEVEL"]);
  620. targetAttributeKeys.removeString ("GCC_OPTIMIZATION_LEVEL");
  621. }
  622. if (targetAttributeKeys.contains ("LLVM_LTO"))
  623. {
  624. cppFlags.add ("-flto");
  625. targetAttributeKeys.removeString ("LLVM_LTO");
  626. }
  627. if (targetAttributeKeys.contains ("GCC_FAST_MATH"))
  628. {
  629. cppFlags.add ("-ffast-math");
  630. targetAttributeKeys.removeString ("GCC_FAST_MATH");
  631. }
  632. // We'll take this setting from the project
  633. targetAttributeKeys.removeString ("CLANG_CXX_LANGUAGE_STANDARD");
  634. if (targetAttributeKeys.contains ("CLANG_CXX_LIBRARY"))
  635. {
  636. cppFlags.add ("-stdlib=" + targetAttributes["CLANG_CXX_LIBRARY"].unquoted());
  637. targetAttributeKeys.removeString ("CLANG_CXX_LIBRARY");
  638. }
  639. out << "target_compile_options (" << targetVarName << " PRIVATE" << newLine;
  640. for (auto& flag : cppFlags)
  641. out << " " << flag << newLine;
  642. out << ")" << newLine << newLine;
  643. StringArray libSearchPaths;
  644. if (targetAttributeKeys.contains ("LIBRARY_SEARCH_PATHS"))
  645. {
  646. auto paths = targetAttributes["LIBRARY_SEARCH_PATHS"].trim().substring (1).dropLastCharacters (1);
  647. paths = paths.replace ("\"$(inherited)\"", {});
  648. paths = paths.replace ("$(HOME)", "$ENV{HOME}");
  649. libSearchPaths.addTokens (paths, ",\"\t\\", {});
  650. libSearchPaths.removeEmptyStrings();
  651. for (auto& libPath : libSearchPaths)
  652. {
  653. libPath = libPath.replace ("${CURRENT_ARCH}", archLabel);
  654. if (! isUnixAbsolutePath (libPath))
  655. libPath = "${CMAKE_CURRENT_SOURCE_DIR}/" + libPath;
  656. }
  657. targetAttributeKeys.removeString ("LIBRARY_SEARCH_PATHS");
  658. }
  659. StringArray linkerFlags;
  660. if (targetAttributeKeys.contains ("OTHER_LDFLAGS"))
  661. {
  662. // CMake adds its own SHARED_CODE library linking flags
  663. auto flagsWithReplacedSpaces = targetAttributes["OTHER_LDFLAGS"].unquoted().replace ("\\\\ ", "^^%%^^");
  664. linkerFlags.addTokens (flagsWithReplacedSpaces, true);
  665. linkerFlags.removeString ("-bundle");
  666. linkerFlags.removeString ("-l" + binaryName.replace (" ", "^^%%^^"));
  667. for (auto& flag : linkerFlags)
  668. flag = flag.replace ("^^%%^^", " ");
  669. targetAttributeKeys.removeString ("OTHER_LDFLAGS");
  670. }
  671. if (target->type == ProjectType::Target::Type::AudioUnitPlugIn)
  672. {
  673. String rezFlags;
  674. if (targetAttributeKeys.contains ("OTHER_REZFLAGS"))
  675. {
  676. rezFlags = targetAttributes["OTHER_REZFLAGS"];
  677. targetAttributeKeys.removeString ("OTHER_REZFLAGS");
  678. }
  679. for (auto& item : exporter.getAllGroups())
  680. {
  681. if (item.getName() == ProjectSaver::getJuceCodeGroupName())
  682. {
  683. String resSourcesVar (targetVarName + "_REZ_SOURCES");
  684. String resOutputVar (targetVarName + "_REZ_OUTPUT");
  685. out << "if (RC_COMPILER)" << newLine
  686. << " set (" << resSourcesVar << newLine
  687. << " \"" << item.determineGroupFolder().getFullPathName() + "/include_juce_audio_plugin_client_AU.r\"" << newLine
  688. << " )" << newLine
  689. << " set (" << resOutputVar << " \"${CMAKE_CURRENT_BINARY_DIR}/" << binaryName << ".rsrc\")" << newLine
  690. << " target_sources (" << targetVarName << " PRIVATE" << newLine
  691. << " ${" << resSourcesVar << "}" << newLine
  692. << " ${" << resOutputVar << "}" << newLine
  693. << " )" << newLine
  694. << " execute_process (COMMAND" << newLine
  695. << " ${RC_COMPILER}" << newLine
  696. << " " << rezFlags.unquoted().removeCharacters ("\\") << newLine;
  697. for (auto& path : headerSearchPaths)
  698. {
  699. out << " -I \"";
  700. if (! isUnixAbsolutePath (path))
  701. out << "${PROJECT_SOURCE_DIR}/";
  702. out << path << "\"" << newLine;
  703. }
  704. out << " ${" << resSourcesVar << "}" << newLine
  705. << " -o ${" << resOutputVar << "}" << newLine
  706. << " )" << newLine
  707. << " set_source_files_properties (${" << resOutputVar << "} PROPERTIES" << newLine
  708. << " GENERATED TRUE" << newLine
  709. << " MACOSX_PACKAGE_LOCATION Resources" << newLine
  710. << " )" << newLine
  711. << "endif (RC_COMPILER)" << newLine << newLine;
  712. break;
  713. }
  714. }
  715. }
  716. if (targetAttributeKeys.contains ("INFOPLIST_FILE"))
  717. {
  718. auto plistFile = exporter.getTargetFolder().getChildFile (targetAttributes["INFOPLIST_FILE"]);
  719. XmlDocument infoPlistData (plistFile);
  720. ScopedPointer<XmlElement> plist = infoPlistData.getDocumentElement();
  721. if (plist != nullptr)
  722. {
  723. if (auto* dict = plist->getChildByName ("dict"))
  724. {
  725. if (auto* entry = dict->getChildByName ("key"))
  726. {
  727. while (entry != nullptr)
  728. {
  729. if (entry->getAllSubText() == "CFBundleExecutable")
  730. {
  731. if (auto* bundleName = entry->getNextElementWithTagName ("string"))
  732. {
  733. bundleName->deleteAllTextElements();
  734. bundleName->addTextElement (binaryName);
  735. }
  736. }
  737. entry = entry->getNextElementWithTagName ("key");
  738. }
  739. }
  740. }
  741. File updatedPlist (getTargetFolder().getChildFile (config.getName() + "-" + plistFile.getFileName()));
  742. plist->writeToFile (updatedPlist, "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
  743. targetAttributes.set ("INFOPLIST_FILE", updatedPlist.getFullPathName().quoted());
  744. }
  745. else
  746. {
  747. targetAttributeKeys.removeString ("INFOPLIST_FILE");
  748. }
  749. }
  750. targetAttributeKeys.sort (false);
  751. out << "set_target_properties (" << targetVarName << " PROPERTIES" << newLine
  752. << " OUTPUT_NAME " << binaryName.quoted() << newLine;
  753. auto cxxStandard = project.getCppStandardString();
  754. if (cxxStandard == "latest")
  755. cxxStandard = "1z";
  756. out << " CXX_STANDARD " << cxxStandard << newLine;
  757. if (! shouldUseGNUExtensions())
  758. out << " CXX_EXTENSIONS OFF" << newLine;
  759. for (auto& key : targetAttributeKeys)
  760. out << " XCODE_ATTRIBUTE_" << key << " " << targetAttributes[key] << newLine;
  761. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::executable
  762. || target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle)
  763. {
  764. out << " MACOSX_BUNDLE_INFO_PLIST " << targetAttributes.getValue ("INFOPLIST_FILE", "\"\"") << newLine
  765. << " XCODE_ATTRIBUTE_PRODUCT_NAME " << binaryName.quoted() << newLine;
  766. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::executable)
  767. {
  768. out << " MACOSX_BUNDLE TRUE" << newLine;
  769. }
  770. else
  771. {
  772. out << " BUNDLE TRUE" << newLine
  773. << " BUNDLE_EXTENSION " << targetAttributes.getValue ("WRAPPER_EXTENSION", "\"\"") << newLine
  774. << " XCODE_ATTRIBUTE_MACH_O_TYPE \"mh_bundle\"" << newLine;
  775. }
  776. }
  777. out << ")" << newLine << newLine;
  778. out << "target_link_libraries (" << targetVarName << " PRIVATE" << newLine;
  779. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  780. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  781. out << " SHARED_CODE" << newLine;
  782. for (auto& path : libSearchPaths)
  783. out << " \"-L\\\"" << path << "\\\"\"" << newLine;
  784. for (auto& flag : linkerFlags)
  785. out << " " << flag.quoted() << newLine;
  786. for (auto& framework : target->frameworkNames)
  787. out << " \"-framework " << framework << "\"" << newLine;
  788. out << ")" << newLine << newLine;
  789. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  790. || target->type == ProjectType::Target::Type::StandalonePlugIn)
  791. {
  792. if (target->getTargetFileType() == ProjectType::Target::TargetFileType::pluginBundle
  793. && targetAttributeKeys.contains("INSTALL_PATH"))
  794. {
  795. const auto installPath = targetAttributes["INSTALL_PATH"].unquoted().replace ("$(HOME)", "$ENV{HOME}");
  796. const auto productFilename = binaryName + (targetAttributeKeys.contains ("WRAPPER_EXTENSION") ? "." + targetAttributes["WRAPPER_EXTENSION"] : String());
  797. auto productPath = (installPath + productFilename).quoted();
  798. out << "add_custom_command (TARGET " << targetVarName << " POST_BUILD" << newLine
  799. << " COMMAND cmake -E remove_directory " << productPath << newLine
  800. << " COMMAND cmake -E copy_directory \"${CMAKE_BINARY_DIR}/" << productFilename << "\" " << productPath << newLine
  801. << " COMMENT \"Copying \\\"" << productFilename << "\\\" to \\\"" << installPath.unquoted() << "\\\"\"" << newLine
  802. << ")" << newLine << newLine;
  803. }
  804. }
  805. }
  806. std::map<String, String> basicWarnings
  807. {
  808. { "CLANG_WARN_BOOL_CONVERSION", "bool-conversion" },
  809. { "CLANG_WARN_COMMA", "comma" },
  810. { "CLANG_WARN_CONSTANT_CONVERSION", "constant-conversion" },
  811. { "CLANG_WARN_EMPTY_BODY", "empty-body" },
  812. { "CLANG_WARN_ENUM_CONVERSION", "enum-conversion" },
  813. { "CLANG_WARN_INFINITE_RECURSION", "infinite-recursion" },
  814. { "CLANG_WARN_INT_CONVERSION", "int-conversion" },
  815. { "CLANG_WARN_RANGE_LOOP_ANALYSIS", "range-loop-analysis" },
  816. { "CLANG_WARN_STRICT_PROTOTYPES", "strict-prototypes" },
  817. { "GCC_WARN_CHECK_SWITCH_STATEMENTS", "switch" },
  818. { "GCC_WARN_UNUSED_VARIABLE", "unused-variable" },
  819. { "GCC_WARN_MISSING_PARENTHESES", "parentheses" },
  820. { "GCC_WARN_NON_VIRTUAL_DESTRUCTOR", "non-virtual-dtor" },
  821. { "GCC_WARN_64_TO_32_BIT_CONVERSION", "shorten-64-to-32" },
  822. { "GCC_WARN_UNDECLARED_SELECTOR", "undeclared-selector" },
  823. { "GCC_WARN_UNUSED_FUNCTION", "unused-function" }
  824. };
  825. StringArray compilerFlags;
  826. for (auto& key : configSettingsKeys)
  827. {
  828. auto basicWarning = basicWarnings.find (key);
  829. if (basicWarning != basicWarnings.end())
  830. {
  831. compilerFlags.add (configSettings[key] == "YES" ? "-W" + basicWarning->second : "-Wno-" + basicWarning->second);
  832. }
  833. else if (key == "CLANG_WARN_SUSPICIOUS_MOVE" && configSettings[key] == "YES") compilerFlags.add ("-Wmove");
  834. else if (key == "CLANG_WARN_UNREACHABLE_CODE" && configSettings[key] == "YES") compilerFlags.add ("-Wunreachable-code");
  835. else if (key == "CLANG_WARN__DUPLICATE_METHOD_MATCH" && configSettings[key] == "YES") compilerFlags.add ("-Wduplicate-method-match");
  836. else if (key == "GCC_INLINES_ARE_PRIVATE_EXTERN" && configSettings[key] == "YES") compilerFlags.add ("-fvisibility-inlines-hidden");
  837. else if (key == "GCC_NO_COMMON_BLOCKS" && configSettings[key] == "YES") compilerFlags.add ("-fno-common");
  838. else if (key == "GCC_WARN_ABOUT_RETURN_TYPE" && configSettings[key] != "YES") compilerFlags.add (configSettings[key] == "YES_ERROR" ? "-Werror=return-type" : "-Wno-return-type");
  839. else if (key == "GCC_WARN_TYPECHECK_CALLS_TO_PRINTF" && configSettings[key] != "YES") compilerFlags.add ("-Wno-format");
  840. else if (key == "GCC_WARN_UNINITIALIZED_AUTOS")
  841. {
  842. if (configSettings[key] == "YES") compilerFlags.add ("-Wuninitialized");
  843. else if (configSettings[key] == "YES_AGGRESSIVE") compilerFlags.add ("--Wconditional-uninitialized");
  844. else compilerFlags.add (")-Wno-uninitialized");
  845. }
  846. else if (key == "WARNING_CFLAGS") compilerFlags.add (configSettings[key]);
  847. }
  848. out << addToCMakeVariable ("CMAKE_CXX_FLAGS", compilerFlags.joinIntoString (" ")) << newLine
  849. << addToCMakeVariable ("CMAKE_C_FLAGS", "${CMAKE_CXX_FLAGS}") << newLine
  850. << newLine;
  851. out << "endif (" << buildTypeCondition << ")" << newLine
  852. << newLine;
  853. }
  854. }
  855. //==============================================================================
  856. JUCE_DECLARE_NON_COPYABLE (CLionProjectExporter)
  857. };