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.

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