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.

956 lines
36KB

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