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.

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