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.

1110 lines
51KB

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