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.

1084 lines
44KB

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