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.

1027 lines
40KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #pragma once
  14. //==============================================================================
  15. class MakefileProjectExporter : public ProjectExporter
  16. {
  17. protected:
  18. //==============================================================================
  19. class MakeBuildConfiguration : public BuildConfiguration
  20. {
  21. public:
  22. MakeBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
  23. : BuildConfiguration (p, settings, e),
  24. architectureTypeValue (config, Ids::linuxArchitecture, getUndoManager(), String()),
  25. pluginBinaryCopyStepValue (config, Ids::enablePluginBinaryCopyStep, getUndoManager(), true),
  26. vstBinaryLocation (config, Ids::vstBinaryLocation, getUndoManager(), "$(HOME)/.vst"),
  27. vst3BinaryLocation (config, Ids::vst3BinaryLocation, getUndoManager(), "$(HOME)/.vst3"),
  28. unityPluginBinaryLocation (config, Ids::unityPluginBinaryLocation, getUndoManager(), "$(HOME)/UnityPlugins")
  29. {
  30. linkTimeOptimisationValue.setDefault (false);
  31. optimisationLevelValue.setDefault (isDebug() ? gccO0 : gccO3);
  32. }
  33. void createConfigProperties (PropertyListBuilder& props) override
  34. {
  35. addRecommendedLinuxCompilerWarningsProperty (props);
  36. addGCCOptimisationProperty (props);
  37. props.add (new ChoicePropertyComponent (architectureTypeValue, "Architecture",
  38. { "<None>", "Native", "32-bit (-m32)", "64-bit (-m64)", "ARM v6", "ARM v7" },
  39. { { String() }, "-march=native", "-m32", "-m64", "-march=armv6", "-march=armv7" }),
  40. "Specifies the 32/64-bit architecture to use.");
  41. auto isBuildingAnyPlugins = (project.shouldBuildVST() || project.shouldBuildVST3() || project.shouldBuildUnityPlugin());
  42. if (isBuildingAnyPlugins)
  43. {
  44. props.add (new ChoicePropertyComponent (pluginBinaryCopyStepValue, "Enable Plugin Copy Step"),
  45. "Enable this to copy plugin binaries to a specified folder after building.");
  46. if (project.shouldBuildVST3())
  47. props.add (new TextPropertyComponentWithEnablement (vst3BinaryLocation, pluginBinaryCopyStepValue, "VST3 Binary Location",
  48. 1024, false),
  49. "The folder in which the compiled VST3 binary should be placed.");
  50. if (project.shouldBuildUnityPlugin())
  51. props.add (new TextPropertyComponentWithEnablement (unityPluginBinaryLocation, pluginBinaryCopyStepValue, "Unity Binary Location",
  52. 1024, false),
  53. "The folder in which the compiled Unity plugin binary and associated C# GUI script should be placed.");
  54. if (project.shouldBuildVST())
  55. props.add (new TextPropertyComponentWithEnablement (vstBinaryLocation, pluginBinaryCopyStepValue, "VST (Legacy) Binary Location",
  56. 1024, false),
  57. "The folder in which the compiled legacy VST binary should be placed.");
  58. }
  59. }
  60. String getModuleLibraryArchName() const override
  61. {
  62. auto archFlag = getArchitectureTypeString();
  63. String prefix ("-march=");
  64. if (archFlag.startsWith (prefix))
  65. return archFlag.substring (prefix.length());
  66. if (archFlag == "-m64")
  67. return "x86_64";
  68. if (archFlag == "-m32")
  69. return "i386";
  70. return "${JUCE_ARCH_LABEL}";
  71. }
  72. String getArchitectureTypeString() const { return architectureTypeValue.get(); }
  73. bool isPluginBinaryCopyStepEnabled() const { return pluginBinaryCopyStepValue.get(); }
  74. String getVSTBinaryLocationString() const { return vstBinaryLocation.get(); }
  75. String getVST3BinaryLocationString() const { return vst3BinaryLocation.get(); }
  76. String getUnityPluginBinaryLocationString() const { return unityPluginBinaryLocation.get(); }
  77. private:
  78. //==============================================================================
  79. ValueWithDefault architectureTypeValue, pluginBinaryCopyStepValue, vstBinaryLocation, vst3BinaryLocation, unityPluginBinaryLocation;
  80. };
  81. BuildConfiguration::Ptr createBuildConfig (const ValueTree& tree) const override
  82. {
  83. return *new MakeBuildConfiguration (project, tree, *this);
  84. }
  85. public:
  86. //==============================================================================
  87. class MakefileTarget : public build_tools::ProjectType::Target
  88. {
  89. public:
  90. MakefileTarget (build_tools::ProjectType::Target::Type targetType, const MakefileProjectExporter& exporter)
  91. : build_tools::ProjectType::Target (targetType), owner (exporter)
  92. {}
  93. StringArray getCompilerFlags() const
  94. {
  95. StringArray result;
  96. if (getTargetFileType() == sharedLibraryOrDLL || getTargetFileType() == pluginBundle)
  97. {
  98. result.add ("-fPIC");
  99. result.add ("-fvisibility=hidden");
  100. }
  101. return result;
  102. }
  103. StringArray getLinkerFlags() const
  104. {
  105. StringArray result;
  106. if (getTargetFileType() == sharedLibraryOrDLL || getTargetFileType() == pluginBundle)
  107. {
  108. result.add ("-shared");
  109. if (getTargetFileType() == pluginBundle)
  110. result.add ("-Wl,--no-undefined");
  111. }
  112. return result;
  113. }
  114. StringPairArray getDefines (const BuildConfiguration& config) const
  115. {
  116. StringPairArray result;
  117. auto commonOptionKeys = owner.getAllPreprocessorDefs (config, build_tools::ProjectType::Target::unspecified).getAllKeys();
  118. auto targetSpecific = owner.getAllPreprocessorDefs (config, type);
  119. for (auto& key : targetSpecific.getAllKeys())
  120. if (! commonOptionKeys.contains (key))
  121. result.set (key, targetSpecific[key]);
  122. return result;
  123. }
  124. StringArray getTargetSettings (const MakeBuildConfiguration& config) const
  125. {
  126. if (type == AggregateTarget) // the aggregate target should not specify any settings at all!
  127. return {}; // it just defines dependencies on the other targets.
  128. StringArray s;
  129. auto cppflagsVarName = "JUCE_CPPFLAGS_" + getTargetVarName();
  130. s.add (cppflagsVarName + " := " + createGCCPreprocessorFlags (getDefines (config)));
  131. auto cflags = getCompilerFlags();
  132. if (! cflags.isEmpty())
  133. s.add ("JUCE_CFLAGS_" + getTargetVarName() + " := " + cflags.joinIntoString (" "));
  134. auto ldflags = getLinkerFlags();
  135. if (! ldflags.isEmpty())
  136. s.add ("JUCE_LDFLAGS_" + getTargetVarName() + " := " + ldflags.joinIntoString (" "));
  137. auto targetName = owner.replacePreprocessorTokens (config, config.getTargetBinaryNameString());
  138. if (owner.projectType.isStaticLibrary())
  139. targetName = getStaticLibbedFilename (targetName);
  140. else if (owner.projectType.isDynamicLibrary())
  141. targetName = getDynamicLibbedFilename (targetName);
  142. else
  143. targetName = targetName.upToLastOccurrenceOf (".", false, false) + getTargetFileSuffix();
  144. if (type == VST3PlugIn)
  145. {
  146. s.add ("JUCE_VST3DIR := " + targetName.upToLastOccurrenceOf (".", false, false) + ".vst3");
  147. auto is32Bit = config.getArchitectureTypeString().contains ("m32");
  148. s.add (String ("JUCE_VST3SUBDIR := Contents/") + (is32Bit ? "i386" : "x86_64") + "-linux");
  149. targetName = "$(JUCE_VST3DIR)/$(JUCE_VST3SUBDIR)/" + targetName;
  150. }
  151. else if (type == UnityPlugIn)
  152. {
  153. s.add ("JUCE_UNITYDIR := Unity");
  154. targetName = "$(JUCE_UNITYDIR)/" + targetName;
  155. }
  156. s.add ("JUCE_TARGET_" + getTargetVarName() + String (" := ") + escapeSpaces (targetName));
  157. if (config.isPluginBinaryCopyStepEnabled() && (type == VST3PlugIn || type == VSTPlugIn || type == UnityPlugIn))
  158. {
  159. String copyCmd ("JUCE_COPYCMD_" + getTargetVarName() + String (" := $(JUCE_OUTDIR)/"));
  160. if (type == VST3PlugIn) s.add (copyCmd + "$(JUCE_VST3DIR) " + config.getVST3BinaryLocationString());
  161. else if (type == VSTPlugIn) s.add (copyCmd + targetName + " " + config.getVSTBinaryLocationString());
  162. else if (type == UnityPlugIn) s.add (copyCmd + "$(JUCE_UNITYDIR)/. " + config.getUnityPluginBinaryLocationString());
  163. }
  164. return s;
  165. }
  166. String getTargetFileSuffix() const
  167. {
  168. if (type == VSTPlugIn || type == VST3PlugIn || type == UnityPlugIn || type == DynamicLibrary)
  169. return ".so";
  170. if (type == SharedCodeTarget || type == StaticLibrary)
  171. return ".a";
  172. return {};
  173. }
  174. String getTargetVarName() const
  175. {
  176. return String (getName()).toUpperCase().replaceCharacter (L' ', L'_');
  177. }
  178. void writeObjects (OutputStream& out, const Array<std::pair<File, String>>& filesToCompile) const
  179. {
  180. out << "OBJECTS_" + getTargetVarName() + String (" := \\") << newLine;
  181. for (auto& f : filesToCompile)
  182. out << " $(JUCE_OBJDIR)/" << escapeSpaces (owner.getObjectFileFor ({ f.first, owner.getTargetFolder(), build_tools::RelativePath::buildTargetFolder })) << " \\" << newLine;
  183. out << newLine;
  184. }
  185. void addFiles (OutputStream& out, const Array<std::pair<File, String>>& filesToCompile)
  186. {
  187. auto cppflagsVarName = "JUCE_CPPFLAGS_" + getTargetVarName();
  188. auto cflagsVarName = "JUCE_CFLAGS_" + getTargetVarName();
  189. for (auto& f : filesToCompile)
  190. {
  191. build_tools::RelativePath relativePath (f.first, owner.getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
  192. out << "$(JUCE_OBJDIR)/" << escapeSpaces (owner.getObjectFileFor (relativePath)) << ": " << escapeSpaces (relativePath.toUnixStyle()) << newLine
  193. << "\t-$(V_AT)mkdir -p $(JUCE_OBJDIR)" << newLine
  194. << "\t@echo \"Compiling " << relativePath.getFileName() << "\"" << newLine
  195. << (relativePath.hasFileExtension ("c;s;S") ? "\t$(V_AT)$(CC) $(JUCE_CFLAGS) " : "\t$(V_AT)$(CXX) $(JUCE_CXXFLAGS) ")
  196. << "$(" << cppflagsVarName << ") $(" << cflagsVarName << ")"
  197. << (f.second.isNotEmpty() ? " $(" + owner.getCompilerFlagSchemeVariableName (f.second) + ")" : "") << " -o \"$@\" -c \"$<\"" << newLine
  198. << newLine;
  199. }
  200. }
  201. String getBuildProduct() const
  202. {
  203. return "$(JUCE_OUTDIR)/$(JUCE_TARGET_" + getTargetVarName() + ")";
  204. }
  205. String getPhonyName() const
  206. {
  207. return String (getName()).upToFirstOccurrenceOf (" ", false, false);
  208. }
  209. void writeTargetLine (OutputStream& out, const StringArray& packages)
  210. {
  211. jassert (type != AggregateTarget);
  212. out << getBuildProduct() << " : "
  213. << "$(OBJECTS_" << getTargetVarName() << ") $(RESOURCES)";
  214. if (type != SharedCodeTarget && owner.shouldBuildTargetType (SharedCodeTarget))
  215. out << " $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE)";
  216. out << newLine;
  217. if (! packages.isEmpty())
  218. {
  219. out << "\t@command -v pkg-config >/dev/null 2>&1 || { echo >&2 \"pkg-config not installed. Please, install it.\"; exit 1; }" << newLine
  220. << "\t@pkg-config --print-errors";
  221. for (auto& pkg : packages)
  222. out << " " << pkg;
  223. out << newLine;
  224. }
  225. out << "\t@echo Linking \"" << owner.projectName << " - " << getName() << "\"" << newLine
  226. << "\t-$(V_AT)mkdir -p $(JUCE_BINDIR)" << newLine
  227. << "\t-$(V_AT)mkdir -p $(JUCE_LIBDIR)" << newLine
  228. << "\t-$(V_AT)mkdir -p $(JUCE_OUTDIR)" << newLine;
  229. if (type == VST3PlugIn)
  230. out << "\t-$(V_AT)mkdir -p $(JUCE_OUTDIR)/$(JUCE_VST3DIR)/$(JUCE_VST3SUBDIR)" << newLine;
  231. else if (type == UnityPlugIn)
  232. out << "\t-$(V_AT)mkdir -p $(JUCE_OUTDIR)/$(JUCE_UNITYDIR)" << newLine;
  233. if (owner.projectType.isStaticLibrary() || type == SharedCodeTarget)
  234. {
  235. out << "\t$(V_AT)$(AR) -rcs " << getBuildProduct()
  236. << " $(OBJECTS_" << getTargetVarName() << ")" << newLine;
  237. }
  238. else
  239. {
  240. out << "\t$(V_AT)$(CXX) -o " << getBuildProduct()
  241. << " $(OBJECTS_" << getTargetVarName() << ") ";
  242. if (owner.shouldBuildTargetType (SharedCodeTarget))
  243. out << "$(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) ";
  244. out << "$(JUCE_LDFLAGS) ";
  245. if (getTargetFileType() == sharedLibraryOrDLL || getTargetFileType() == pluginBundle
  246. || type == GUIApp || type == StandalonePlugIn)
  247. out << "$(JUCE_LDFLAGS_" << getTargetVarName() << ") ";
  248. out << "$(RESOURCES) $(TARGET_ARCH)" << newLine;
  249. }
  250. if (type == VST3PlugIn)
  251. {
  252. out << "\t-$(V_AT)cp -R $(JUCE_COPYCMD_VST3)" << newLine;
  253. }
  254. else if (type == VSTPlugIn)
  255. {
  256. out << "\t-$(V_AT)cp $(JUCE_COPYCMD_VST)" << newLine;
  257. }
  258. else if (type == UnityPlugIn)
  259. {
  260. auto scriptName = owner.getProject().getUnityScriptName();
  261. build_tools::RelativePath scriptPath (owner.getProject().getGeneratedCodeFolder().getChildFile (scriptName),
  262. owner.getTargetFolder(),
  263. build_tools::RelativePath::projectFolder);
  264. out << "\t-$(V_AT)cp " + scriptPath.toUnixStyle() + " $(JUCE_OUTDIR)/$(JUCE_UNITYDIR)" << newLine
  265. << "\t-$(V_AT)cp -R $(JUCE_COPYCMD_UNITY_PLUGIN)" << newLine;
  266. }
  267. out << newLine;
  268. }
  269. const MakefileProjectExporter& owner;
  270. };
  271. //==============================================================================
  272. static const char* getNameLinux() { return "Linux Makefile"; }
  273. static const char* getValueTreeTypeName() { return "LINUX_MAKE"; }
  274. String getExtraPkgConfigString() const { return extraPkgConfigValue.get(); }
  275. static MakefileProjectExporter* createForSettings (Project& projectToUse, const ValueTree& settingsToUse)
  276. {
  277. if (settingsToUse.hasType (getValueTreeTypeName()))
  278. return new MakefileProjectExporter (projectToUse, settingsToUse);
  279. return nullptr;
  280. }
  281. //==============================================================================
  282. MakefileProjectExporter (Project& p, const ValueTree& t)
  283. : ProjectExporter (p, t),
  284. extraPkgConfigValue (settings, Ids::linuxExtraPkgConfig, getUndoManager())
  285. {
  286. name = getNameLinux();
  287. targetLocationValue.setDefault (getDefaultBuildsRootFolder() + getTargetFolderForExporter (getValueTreeTypeName()));
  288. }
  289. //==============================================================================
  290. bool canLaunchProject() override { return false; }
  291. bool launchProject() override { return false; }
  292. bool usesMMFiles() const override { return false; }
  293. bool canCopeWithDuplicateFiles() override { return false; }
  294. bool supportsUserDefinedConfigurations() const override { return true; }
  295. bool isXcode() const override { return false; }
  296. bool isVisualStudio() const override { return false; }
  297. bool isCodeBlocks() const override { return false; }
  298. bool isMakefile() const override { return true; }
  299. bool isAndroidStudio() const override { return false; }
  300. bool isCLion() const override { return false; }
  301. bool isAndroid() const override { return false; }
  302. bool isWindows() const override { return false; }
  303. bool isLinux() const override { return true; }
  304. bool isOSX() const override { return false; }
  305. bool isiOS() const override { return false; }
  306. bool supportsTargetType (build_tools::ProjectType::Target::Type type) const override
  307. {
  308. switch (type)
  309. {
  310. case build_tools::ProjectType::Target::GUIApp:
  311. case build_tools::ProjectType::Target::ConsoleApp:
  312. case build_tools::ProjectType::Target::StaticLibrary:
  313. case build_tools::ProjectType::Target::SharedCodeTarget:
  314. case build_tools::ProjectType::Target::AggregateTarget:
  315. case build_tools::ProjectType::Target::VSTPlugIn:
  316. case build_tools::ProjectType::Target::VST3PlugIn:
  317. case build_tools::ProjectType::Target::StandalonePlugIn:
  318. case build_tools::ProjectType::Target::DynamicLibrary:
  319. case build_tools::ProjectType::Target::UnityPlugIn:
  320. return true;
  321. case build_tools::ProjectType::Target::AAXPlugIn:
  322. case build_tools::ProjectType::Target::RTASPlugIn:
  323. case build_tools::ProjectType::Target::AudioUnitPlugIn:
  324. case build_tools::ProjectType::Target::AudioUnitv3PlugIn:
  325. case build_tools::ProjectType::Target::unspecified:
  326. default:
  327. break;
  328. }
  329. return false;
  330. }
  331. void createExporterProperties (PropertyListBuilder& properties) override
  332. {
  333. properties.add (new TextPropertyComponent (extraPkgConfigValue, "pkg-config libraries", 8192, false),
  334. "Extra pkg-config libraries for you application. Each package should be space separated.");
  335. }
  336. void initialiseDependencyPathValues() override
  337. {
  338. vstLegacyPathValueWrapper.init ({ settings, Ids::vstLegacyFolder, nullptr },
  339. getAppSettings().getStoredPath (Ids::vstLegacyPath, TargetOS::linux), TargetOS::linux);
  340. }
  341. //==============================================================================
  342. bool anyTargetIsSharedLibrary() const
  343. {
  344. for (auto* target : targets)
  345. {
  346. auto fileType = target->getTargetFileType();
  347. if (fileType == build_tools::ProjectType::Target::sharedLibraryOrDLL
  348. || fileType == build_tools::ProjectType::Target::pluginBundle)
  349. return true;
  350. }
  351. return false;
  352. }
  353. //==============================================================================
  354. void create (const OwnedArray<LibraryModule>&) const override
  355. {
  356. build_tools::writeStreamToFile (getTargetFolder().getChildFile ("Makefile"), [&] (MemoryOutputStream& mo)
  357. {
  358. mo.setNewLineString ("\n");
  359. writeMakefile (mo);
  360. });
  361. }
  362. //==============================================================================
  363. void addPlatformSpecificSettingsForProjectType (const build_tools::ProjectType&) override
  364. {
  365. callForAllSupportedTargets ([this] (build_tools::ProjectType::Target::Type targetType)
  366. {
  367. targets.insert (targetType == build_tools::ProjectType::Target::AggregateTarget ? 0 : -1,
  368. new MakefileTarget (targetType, *this));
  369. });
  370. // If you hit this assert, you tried to generate a project for an exporter
  371. // that does not support any of your targets!
  372. jassert (targets.size() > 0);
  373. }
  374. private:
  375. ValueWithDefault extraPkgConfigValue;
  376. //==============================================================================
  377. StringPairArray getDefines (const BuildConfiguration& config) const
  378. {
  379. StringPairArray result;
  380. result.set ("LINUX", "1");
  381. if (config.isDebug())
  382. {
  383. result.set ("DEBUG", "1");
  384. result.set ("_DEBUG", "1");
  385. }
  386. else
  387. {
  388. result.set ("NDEBUG", "1");
  389. }
  390. result = mergePreprocessorDefs (result, getAllPreprocessorDefs (config, build_tools::ProjectType::Target::unspecified));
  391. return result;
  392. }
  393. StringArray getPackages() const
  394. {
  395. StringArray packages;
  396. packages.addTokens (getExtraPkgConfigString(), " ", "\"'");
  397. packages.removeEmptyStrings();
  398. packages.addArray (linuxPackages);
  399. // don't add libcurl if curl symbols are loaded at runtime
  400. if (isCurlEnabled() && ! isLoadCurlSymbolsLazilyEnabled())
  401. packages.add ("libcurl");
  402. packages.removeDuplicates (false);
  403. return packages;
  404. }
  405. String getPreprocessorPkgConfigFlags() const
  406. {
  407. auto packages = getPackages();
  408. if (isWebBrowserComponentEnabled())
  409. {
  410. packages.add ("webkit2gtk-4.0");
  411. packages.add ("gtk+-x11-3.0");
  412. }
  413. if (packages.size() > 0)
  414. return "$(shell pkg-config --cflags " + packages.joinIntoString (" ") + ")";
  415. return {};
  416. }
  417. String getLinkerPkgConfigFlags() const
  418. {
  419. auto packages = getPackages();
  420. if (packages.size() > 0)
  421. return "$(shell pkg-config --libs " + packages.joinIntoString (" ") + ")";
  422. return {};
  423. }
  424. StringArray getCPreprocessorFlags (const BuildConfiguration&) const
  425. {
  426. StringArray result;
  427. if (linuxLibs.contains("pthread"))
  428. result.add ("-pthread");
  429. return result;
  430. }
  431. StringArray getCFlags (const BuildConfiguration& config) const
  432. {
  433. StringArray result;
  434. if (anyTargetIsSharedLibrary())
  435. result.add ("-fPIC");
  436. if (config.isDebug())
  437. {
  438. result.add ("-g");
  439. result.add ("-ggdb");
  440. }
  441. result.add ("-O" + config.getGCCOptimisationFlag());
  442. if (config.isLinkTimeOptimisationEnabled())
  443. result.add ("-flto");
  444. for (auto& recommended : config.getRecommendedCompilerWarningFlags())
  445. result.add (recommended);
  446. auto extra = replacePreprocessorTokens (config, getExtraCompilerFlagsString()).trim();
  447. if (extra.isNotEmpty())
  448. result.add (extra);
  449. return result;
  450. }
  451. StringArray getCXXFlags() const
  452. {
  453. StringArray result;
  454. auto cppStandard = project.getCppStandardString();
  455. if (cppStandard == "latest")
  456. cppStandard = "17";
  457. cppStandard = "-std=" + String (shouldUseGNUExtensions() ? "gnu++" : "c++") + cppStandard;
  458. result.add (cppStandard);
  459. return result;
  460. }
  461. StringArray getHeaderSearchPaths (const BuildConfiguration& config) const
  462. {
  463. StringArray searchPaths (extraSearchPaths);
  464. searchPaths.addArray (config.getHeaderSearchPaths());
  465. searchPaths = getCleanedStringArray (searchPaths);
  466. StringArray result;
  467. for (auto& path : searchPaths)
  468. result.add (build_tools::unixStylePath (replacePreprocessorTokens (config, path)));
  469. return result;
  470. }
  471. StringArray getLibraryNames (const BuildConfiguration& config) const
  472. {
  473. StringArray result (linuxLibs);
  474. auto libraries = StringArray::fromTokens (getExternalLibrariesString(), ";", "\"'");
  475. libraries.removeEmptyStrings();
  476. for (auto& lib : libraries)
  477. result.add (replacePreprocessorTokens (config, lib).trim());
  478. return result;
  479. }
  480. StringArray getLibrarySearchPaths (const BuildConfiguration& config) const
  481. {
  482. auto result = getSearchPathsFromString (config.getLibrarySearchPathString());
  483. for (auto path : moduleLibSearchPaths)
  484. result.add (path + "/" + config.getModuleLibraryArchName());
  485. return result;
  486. }
  487. StringArray getLinkerFlags (const BuildConfiguration& config) const
  488. {
  489. auto result = makefileExtraLinkerFlags;
  490. result.add ("-fvisibility=hidden");
  491. if (config.isLinkTimeOptimisationEnabled())
  492. result.add ("-flto");
  493. auto extraFlags = getExtraLinkerFlagsString().trim();
  494. if (extraFlags.isNotEmpty())
  495. result.add (replacePreprocessorTokens (config, extraFlags));
  496. return result;
  497. }
  498. bool isWebBrowserComponentEnabled() const
  499. {
  500. static String guiExtrasModule ("juce_gui_extra");
  501. return (project.getEnabledModules().isModuleEnabled (guiExtrasModule)
  502. && project.isConfigFlagEnabled ("JUCE_WEB_BROWSER", true));
  503. }
  504. bool isCurlEnabled() const
  505. {
  506. static String juceCoreModule ("juce_core");
  507. return (project.getEnabledModules().isModuleEnabled (juceCoreModule)
  508. && project.isConfigFlagEnabled ("JUCE_USE_CURL", true));
  509. }
  510. bool isLoadCurlSymbolsLazilyEnabled() const
  511. {
  512. static String juceCoreModule ("juce_core");
  513. return (project.getEnabledModules().isModuleEnabled (juceCoreModule)
  514. && project.isConfigFlagEnabled ("JUCE_LOAD_CURL_SYMBOLS_LAZILY", false));
  515. }
  516. //==============================================================================
  517. void writeDefineFlags (OutputStream& out, const MakeBuildConfiguration& config) const
  518. {
  519. out << createGCCPreprocessorFlags (mergePreprocessorDefs (getDefines (config), getAllPreprocessorDefs (config, build_tools::ProjectType::Target::unspecified)));
  520. }
  521. void writePkgConfigFlags (OutputStream& out) const
  522. {
  523. auto flags = getPreprocessorPkgConfigFlags();
  524. if (flags.isNotEmpty())
  525. out << " " << flags;
  526. }
  527. void writeCPreprocessorFlags (OutputStream& out, const BuildConfiguration& config) const
  528. {
  529. auto flags = getCPreprocessorFlags (config);
  530. if (! flags.isEmpty())
  531. out << " " << flags.joinIntoString (" ");
  532. }
  533. void writeHeaderPathFlags (OutputStream& out, const BuildConfiguration& config) const
  534. {
  535. for (auto& path : getHeaderSearchPaths (config))
  536. out << " -I" << escapeSpaces (path).replace ("~", "$(HOME)");
  537. }
  538. void writeCppFlags (OutputStream& out, const MakeBuildConfiguration& config) const
  539. {
  540. out << " JUCE_CPPFLAGS := $(DEPFLAGS)";
  541. writeDefineFlags (out, config);
  542. writePkgConfigFlags (out);
  543. writeCPreprocessorFlags (out, config);
  544. writeHeaderPathFlags (out, config);
  545. out << " $(CPPFLAGS)" << newLine;
  546. }
  547. void writeLinkerFlags (OutputStream& out, const BuildConfiguration& config) const
  548. {
  549. out << " JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR)";
  550. for (auto path : getLibrarySearchPaths (config))
  551. out << " -L" << escapeSpaces (path).replace ("~", "$(HOME)");
  552. auto pkgConfigFlags = getLinkerPkgConfigFlags();
  553. if (pkgConfigFlags.isNotEmpty())
  554. out << " " << getLinkerPkgConfigFlags();
  555. auto linkerFlags = getLinkerFlags (config).joinIntoString (" ");
  556. if (linkerFlags.isNotEmpty())
  557. out << " " << linkerFlags;
  558. for (auto& libName : getLibraryNames (config))
  559. out << " -l" << libName;
  560. out << " $(LDFLAGS)" << newLine;
  561. }
  562. void writeTargetLines (OutputStream& out, const StringArray& packages) const
  563. {
  564. auto n = targets.size();
  565. for (int i = 0; i < n; ++i)
  566. {
  567. if (auto* target = targets.getUnchecked (i))
  568. {
  569. if (target->type == build_tools::ProjectType::Target::AggregateTarget)
  570. {
  571. StringArray dependencies;
  572. MemoryOutputStream subTargetLines;
  573. for (int j = 0; j < n; ++j)
  574. {
  575. if (i == j) continue;
  576. if (auto* dependency = targets.getUnchecked (j))
  577. {
  578. if (dependency->type != build_tools::ProjectType::Target::SharedCodeTarget)
  579. {
  580. auto phonyName = dependency->getPhonyName();
  581. subTargetLines << phonyName << " : " << dependency->getBuildProduct() << newLine;
  582. dependencies.add (phonyName);
  583. }
  584. }
  585. }
  586. out << "all : " << dependencies.joinIntoString (" ") << newLine << newLine;
  587. out << subTargetLines.toString() << newLine << newLine;
  588. }
  589. else
  590. {
  591. if (! getProject().isAudioPluginProject())
  592. out << "all : " << target->getBuildProduct() << newLine << newLine;
  593. target->writeTargetLine (out, packages);
  594. }
  595. }
  596. }
  597. }
  598. void writeConfig (OutputStream& out, const MakeBuildConfiguration& config) const
  599. {
  600. String buildDirName ("build");
  601. auto intermediatesDirName = buildDirName + "/intermediate/" + config.getName();
  602. auto outputDir = buildDirName;
  603. if (config.getTargetBinaryRelativePathString().isNotEmpty())
  604. {
  605. build_tools::RelativePath binaryPath (config.getTargetBinaryRelativePathString(), build_tools::RelativePath::projectFolder);
  606. outputDir = binaryPath.rebased (projectFolder, getTargetFolder(), build_tools::RelativePath::buildTargetFolder).toUnixStyle();
  607. }
  608. out << "ifeq ($(CONFIG)," << escapeSpaces (config.getName()) << ")" << newLine
  609. << " JUCE_BINDIR := " << escapeSpaces (buildDirName) << newLine
  610. << " JUCE_LIBDIR := " << escapeSpaces (buildDirName) << newLine
  611. << " JUCE_OBJDIR := " << escapeSpaces (intermediatesDirName) << newLine
  612. << " JUCE_OUTDIR := " << escapeSpaces (outputDir) << newLine
  613. << newLine
  614. << " ifeq ($(TARGET_ARCH),)" << newLine
  615. << " TARGET_ARCH := " << getArchFlags (config) << newLine
  616. << " endif" << newLine
  617. << newLine;
  618. writeCppFlags (out, config);
  619. for (auto target : targets)
  620. {
  621. auto lines = target->getTargetSettings (config);
  622. if (lines.size() > 0)
  623. out << " " << lines.joinIntoString ("\n ") << newLine;
  624. out << newLine;
  625. }
  626. out << " JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH)";
  627. auto cflags = getCFlags (config).joinIntoString (" ");
  628. if (cflags.isNotEmpty())
  629. out << " " << cflags;
  630. out << " $(CFLAGS)" << newLine;
  631. out << " JUCE_CXXFLAGS += $(JUCE_CFLAGS)";
  632. auto cxxflags = getCXXFlags().joinIntoString (" ");
  633. if (cxxflags.isNotEmpty())
  634. out << " " << cxxflags;
  635. out << " $(CXXFLAGS)" << newLine;
  636. writeLinkerFlags (out, config);
  637. out << newLine;
  638. out << " CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)" << newLine
  639. << "endif" << newLine
  640. << newLine;
  641. }
  642. void writeIncludeLines (OutputStream& out) const
  643. {
  644. auto n = targets.size();
  645. for (int i = 0; i < n; ++i)
  646. {
  647. if (auto* target = targets.getUnchecked (i))
  648. {
  649. if (target->type == build_tools::ProjectType::Target::AggregateTarget)
  650. continue;
  651. out << "-include $(OBJECTS_" << target->getTargetVarName()
  652. << ":%.o=%.d)" << newLine;
  653. }
  654. }
  655. }
  656. static String getCompilerFlagSchemeVariableName (const String& schemeName) { return "JUCE_COMPILERFLAGSCHEME_" + schemeName; }
  657. void findAllFilesToCompile (const Project::Item& projectItem, Array<std::pair<File, String>>& results) const
  658. {
  659. if (projectItem.isGroup())
  660. {
  661. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  662. findAllFilesToCompile (projectItem.getChild (i), results);
  663. }
  664. else
  665. {
  666. if (projectItem.shouldBeCompiled())
  667. {
  668. auto f = projectItem.getFile();
  669. if (shouldFileBeCompiledByDefault (f))
  670. {
  671. auto scheme = projectItem.getCompilerFlagSchemeString();
  672. auto flags = compilerFlagSchemesMap[scheme].get().toString();
  673. if (scheme.isNotEmpty() && flags.isNotEmpty())
  674. results.add ({ f, scheme });
  675. else
  676. results.add ({ f, {} });
  677. }
  678. }
  679. }
  680. }
  681. void writeCompilerFlagSchemes (OutputStream& out, const Array<std::pair<File, String>>& filesToCompile) const
  682. {
  683. StringArray schemesToWrite;
  684. for (auto& f : filesToCompile)
  685. if (f.second.isNotEmpty())
  686. schemesToWrite.addIfNotAlreadyThere (f.second);
  687. if (! schemesToWrite.isEmpty())
  688. {
  689. for (auto& s : schemesToWrite)
  690. out << getCompilerFlagSchemeVariableName (s) << " := "
  691. << compilerFlagSchemesMap[s].get().toString() << newLine;
  692. out << newLine;
  693. }
  694. }
  695. void writeMakefile (OutputStream& out) const
  696. {
  697. out << "# Automatically generated makefile, created by the Projucer" << newLine
  698. << "# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!" << newLine
  699. << newLine;
  700. out << "# build with \"V=1\" for verbose builds" << newLine
  701. << "ifeq ($(V), 1)" << newLine
  702. << "V_AT =" << newLine
  703. << "else" << newLine
  704. << "V_AT = @" << newLine
  705. << "endif" << newLine
  706. << newLine;
  707. out << "# (this disables dependency generation if multiple architectures are set)" << newLine
  708. << "DEPFLAGS := $(if $(word 2, $(TARGET_ARCH)), , -MMD)" << newLine
  709. << newLine;
  710. out << "ifndef STRIP" << newLine
  711. << " STRIP=strip" << newLine
  712. << "endif" << newLine
  713. << newLine;
  714. out << "ifndef AR" << newLine
  715. << " AR=ar" << newLine
  716. << "endif" << newLine
  717. << newLine;
  718. out << "ifndef CONFIG" << newLine
  719. << " CONFIG=" << escapeSpaces (getConfiguration(0)->getName()) << newLine
  720. << "endif" << newLine
  721. << newLine;
  722. out << "JUCE_ARCH_LABEL := $(shell uname -m)" << newLine
  723. << newLine;
  724. for (ConstConfigIterator config (*this); config.next();)
  725. writeConfig (out, dynamic_cast<const MakeBuildConfiguration&> (*config));
  726. Array<std::pair<File, String>> filesToCompile;
  727. for (int i = 0; i < getAllGroups().size(); ++i)
  728. findAllFilesToCompile (getAllGroups().getReference (i), filesToCompile);
  729. writeCompilerFlagSchemes (out, filesToCompile);
  730. auto getFilesForTarget = [] (const Array<std::pair<File, String>>& files, MakefileTarget* target, const Project& p) -> Array<std::pair<File, String>>
  731. {
  732. Array<std::pair<File, String>> targetFiles;
  733. auto targetType = (p.isAudioPluginProject() ? target->type : MakefileTarget::SharedCodeTarget);
  734. for (auto& f : files)
  735. if (p.getTargetTypeFromFilePath (f.first, true) == targetType)
  736. targetFiles.add (f);
  737. return targetFiles;
  738. };
  739. for (auto target : targets)
  740. target->writeObjects (out, getFilesForTarget (filesToCompile, target, project));
  741. out << getPhonyTargetLine() << newLine << newLine;
  742. writeTargetLines (out, getPackages());
  743. for (auto target : targets)
  744. target->addFiles (out, getFilesForTarget (filesToCompile, target, project));
  745. out << "clean:" << newLine
  746. << "\t@echo Cleaning " << projectName << newLine
  747. << "\t$(V_AT)$(CLEANCMD)" << newLine
  748. << newLine;
  749. out << "strip:" << newLine
  750. << "\t@echo Stripping " << projectName << newLine
  751. << "\t-$(V_AT)$(STRIP) --strip-unneeded $(JUCE_OUTDIR)/$(TARGET)" << newLine
  752. << newLine;
  753. writeIncludeLines (out);
  754. }
  755. String getArchFlags (const BuildConfiguration& config) const
  756. {
  757. if (auto* makeConfig = dynamic_cast<const MakeBuildConfiguration*> (&config))
  758. return makeConfig->getArchitectureTypeString();
  759. return "-march=native";
  760. }
  761. String getObjectFileFor (const build_tools::RelativePath& file) const
  762. {
  763. return file.getFileNameWithoutExtension()
  764. + "_" + String::toHexString (file.toUnixStyle().hashCode()) + ".o";
  765. }
  766. String getPhonyTargetLine() const
  767. {
  768. MemoryOutputStream phonyTargetLine;
  769. phonyTargetLine << ".PHONY: clean all strip";
  770. if (! getProject().isAudioPluginProject())
  771. return phonyTargetLine.toString();
  772. for (auto target : targets)
  773. if (target->type != build_tools::ProjectType::Target::SharedCodeTarget
  774. && target->type != build_tools::ProjectType::Target::AggregateTarget)
  775. phonyTargetLine << " " << target->getPhonyName();
  776. return phonyTargetLine.toString();
  777. }
  778. friend class CLionProjectExporter;
  779. OwnedArray<MakefileTarget> targets;
  780. JUCE_DECLARE_NON_COPYABLE (MakefileProjectExporter)
  781. };