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.

963 lines
35KB

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