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.

1091 lines
44KB

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