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.

491 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class MakefileProjectExporter : public ProjectExporter
  18. {
  19. public:
  20. //==============================================================================
  21. static const char* getNameLinux() { return "Linux Makefile"; }
  22. static const char* getValueTreeTypeName() { return "LINUX_MAKE"; }
  23. Value getExtraPkgConfig() { return getSetting (Ids::linuxExtraPkgConfig); }
  24. String getExtraPkgConfigString() const { return getSettingString (Ids::linuxExtraPkgConfig); }
  25. static MakefileProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  26. {
  27. if (settings.hasType (getValueTreeTypeName()))
  28. return new MakefileProjectExporter (project, settings);
  29. return nullptr;
  30. }
  31. //==============================================================================
  32. MakefileProjectExporter (Project& p, const ValueTree& t) : ProjectExporter (p, t)
  33. {
  34. name = getNameLinux();
  35. if (getTargetLocationString().isEmpty())
  36. getTargetLocationValue() = getDefaultBuildsRootFolder() + "LinuxMakefile";
  37. initialiseDependencyPathValues();
  38. }
  39. //==============================================================================
  40. bool canLaunchProject() override { return false; }
  41. bool launchProject() override { return false; }
  42. bool usesMMFiles() const override { return false; }
  43. bool canCopeWithDuplicateFiles() override { return false; }
  44. bool supportsUserDefinedConfigurations() const override { return true; }
  45. bool isXcode() const override { return false; }
  46. bool isVisualStudio() const override { return false; }
  47. bool isCodeBlocks() const override { return false; }
  48. bool isMakefile() const override { return true; }
  49. bool isAndroidStudio() const override { return false; }
  50. bool isAndroidAnt() const override { return false; }
  51. bool isAndroid() const override { return false; }
  52. bool isWindows() const override { return false; }
  53. bool isLinux() const override { return true; }
  54. bool isOSX() const override { return false; }
  55. bool isiOS() const override { return false; }
  56. bool supportsVST() const override { return true; }
  57. bool supportsVST3() const override { return false; }
  58. bool supportsAAX() const override { return false; }
  59. bool supportsRTAS() const override { return false; }
  60. bool supportsAU() const override { return false; }
  61. bool supportsAUv3() const override { return false; }
  62. bool supportsStandalone() const override { return false; }
  63. Value getCppStandardValue() { return getSetting (Ids::cppLanguageStandard); }
  64. String getCppStandardString() const { return settings[Ids::cppLanguageStandard]; }
  65. void createExporterProperties (PropertyListBuilder& properties) override
  66. {
  67. static const char* cppStandardNames[] = { "C++03", "C++11", "C++14", nullptr };
  68. static const char* cppStandardValues[] = { "-std=c++03", "-std=c++11", "-std=c++14", nullptr };
  69. properties.add (new ChoicePropertyComponent (getCppStandardValue(),
  70. "C++ standard to use",
  71. StringArray (cppStandardNames),
  72. Array<var> (cppStandardValues)),
  73. "The C++ standard to specify in the makefile");
  74. properties.add (new TextPropertyComponent (getExtraPkgConfig(), "pkg-config libraries", 8192, false),
  75. "Extra pkg-config libraries for you application. Each package should be space separated.");
  76. }
  77. //==============================================================================
  78. void create (const OwnedArray<LibraryModule>&) const override
  79. {
  80. Array<RelativePath> files;
  81. for (int i = 0; i < getAllGroups().size(); ++i)
  82. findAllFilesToCompile (getAllGroups().getReference(i), files);
  83. MemoryOutputStream mo;
  84. writeMakefile (mo, files);
  85. overwriteFileIfDifferentOrThrow (getTargetFolder().getChildFile ("Makefile"), mo);
  86. }
  87. //==============================================================================
  88. void addPlatformSpecificSettingsForProjectType (const ProjectType& type) override
  89. {
  90. if (type.isStaticLibrary())
  91. makefileTargetSuffix = ".a";
  92. else if (type.isAudioPlugin() || type.isDynamicLibrary())
  93. {
  94. makefileIsDLL = true;
  95. if (type.isDynamicLibrary())
  96. makefileTargetSuffix = ".so";
  97. }
  98. }
  99. protected:
  100. //==============================================================================
  101. class MakeBuildConfiguration : public BuildConfiguration
  102. {
  103. public:
  104. MakeBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
  105. : BuildConfiguration (p, settings, e)
  106. {
  107. }
  108. Value getArchitectureType() { return getValue (Ids::linuxArchitecture); }
  109. var getArchitectureTypeVar() const { return config [Ids::linuxArchitecture]; }
  110. var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); }
  111. void createConfigProperties (PropertyListBuilder& props) override
  112. {
  113. addGCCOptimisationProperty (props);
  114. static const char* const archNames[] = { "(Default)", "<None>", "32-bit (-m32)", "64-bit (-m64)", "ARM v6", "ARM v7" };
  115. const var archFlags[] = { var(), var (String()), "-m32", "-m64", "-march=armv6", "-march=armv7" };
  116. props.add (new ChoicePropertyComponent (getArchitectureType(), "Architecture",
  117. StringArray (archNames, numElementsInArray (archNames)),
  118. Array<var> (archFlags, numElementsInArray (archFlags))));
  119. }
  120. };
  121. BuildConfiguration::Ptr createBuildConfig (const ValueTree& tree) const override
  122. {
  123. return new MakeBuildConfiguration (project, tree, *this);
  124. }
  125. private:
  126. //==============================================================================
  127. void findAllFilesToCompile (const Project::Item& projectItem, Array<RelativePath>& results) const
  128. {
  129. if (projectItem.isGroup())
  130. {
  131. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  132. findAllFilesToCompile (projectItem.getChild(i), results);
  133. }
  134. else
  135. {
  136. if (projectItem.shouldBeCompiled())
  137. results.add (RelativePath (projectItem.getFile(), getTargetFolder(), RelativePath::buildTargetFolder));
  138. }
  139. }
  140. void writeDefineFlags (OutputStream& out, const BuildConfiguration& config) const
  141. {
  142. StringPairArray defines;
  143. defines.set ("LINUX", "1");
  144. if (config.isDebug())
  145. {
  146. defines.set ("DEBUG", "1");
  147. defines.set ("_DEBUG", "1");
  148. }
  149. else
  150. {
  151. defines.set ("NDEBUG", "1");
  152. }
  153. out << createGCCPreprocessorFlags (mergePreprocessorDefs (defines, getAllPreprocessorDefs (config)));
  154. }
  155. void writeHeaderPathFlags (OutputStream& out, const BuildConfiguration& config) const
  156. {
  157. StringArray searchPaths (extraSearchPaths);
  158. searchPaths.addArray (config.getHeaderSearchPaths());
  159. StringArray packages;
  160. packages.addTokens (getExtraPkgConfigString(), " ", "\"'");
  161. packages.removeEmptyStrings();
  162. if (linuxPackages.size() > 0 || packages.size() > 0)
  163. {
  164. out << " $(shell pkg-config --cflags";
  165. for (int i = 0; i < linuxPackages.size(); ++i)
  166. out << " " << linuxPackages[i];
  167. for (int i = 0; i < packages.size(); ++i)
  168. out << " " << packages[i];
  169. out << ")";
  170. }
  171. if (linuxLibs.contains("pthread"))
  172. out << " -pthread";
  173. searchPaths = getCleanedStringArray (searchPaths);
  174. // Replace ~ character with $(HOME) environment variable
  175. for (int i = 0; i < searchPaths.size(); ++i)
  176. out << " -I" << escapeSpaces (FileHelpers::unixStylePath (replacePreprocessorTokens (config, searchPaths[i]))).replace ("~", "$(HOME)");
  177. }
  178. void writeCppFlags (OutputStream& out, const BuildConfiguration& config) const
  179. {
  180. out << " JUCE_CPPFLAGS := $(DEPFLAGS)";
  181. writeDefineFlags (out, config);
  182. writeHeaderPathFlags (out, config);
  183. out << " $(CPPFLAGS)" << newLine;
  184. }
  185. void writeLinkerFlags (OutputStream& out, const BuildConfiguration& config) const
  186. {
  187. out << " JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR)";
  188. {
  189. StringArray flags (makefileExtraLinkerFlags);
  190. if (makefileIsDLL)
  191. flags.add ("-shared");
  192. if (! config.isDebug())
  193. flags.add ("-fvisibility=hidden");
  194. if (flags.size() > 0)
  195. out << " " << getCleanedStringArray (flags).joinIntoString (" ");
  196. }
  197. out << config.getGCCLibraryPathFlags();
  198. StringArray packages;
  199. packages.addTokens (getExtraPkgConfigString(), " ", "\"'");
  200. packages.removeEmptyStrings();
  201. if (linuxPackages.size() > 0 || packages.size() > 0)
  202. {
  203. out << " $(shell pkg-config --libs";
  204. for (int i = 0; i < linuxPackages.size(); ++i)
  205. out << " " << linuxPackages[i];
  206. for (int i = 0; i < packages.size(); ++i)
  207. out << " " << packages[i];
  208. out << ")";
  209. }
  210. for (int i = 0; i < linuxLibs.size(); ++i)
  211. out << " -l" << linuxLibs[i];
  212. StringArray libraries;
  213. libraries.addTokens (getExternalLibrariesString(), ";", "\"'");
  214. libraries.removeEmptyStrings();
  215. if (libraries.size() != 0)
  216. out << " -l" << replacePreprocessorTokens (config, libraries.joinIntoString (" -l")).trim();
  217. out << " " << replacePreprocessorTokens (config, getExtraLinkerFlagsString()).trim()
  218. << " $(LDFLAGS)" << newLine;
  219. }
  220. void writeConfig (OutputStream& out, const BuildConfiguration& config) const
  221. {
  222. const String buildDirName ("build");
  223. const String intermediatesDirName (buildDirName + "/intermediate/" + config.getName());
  224. String outputDir (buildDirName);
  225. if (config.getTargetBinaryRelativePathString().isNotEmpty())
  226. {
  227. RelativePath binaryPath (config.getTargetBinaryRelativePathString(), RelativePath::projectFolder);
  228. outputDir = binaryPath.rebased (projectFolder, getTargetFolder(), RelativePath::buildTargetFolder).toUnixStyle();
  229. }
  230. out << "ifeq ($(CONFIG)," << escapeSpaces (config.getName()) << ")" << newLine;
  231. out << " JUCE_BINDIR := " << escapeSpaces (buildDirName) << newLine
  232. << " JUCE_LIBDIR := " << escapeSpaces (buildDirName) << newLine
  233. << " JUCE_OBJDIR := " << escapeSpaces (intermediatesDirName) << newLine
  234. << " JUCE_OUTDIR := " << escapeSpaces (outputDir) << newLine
  235. << newLine
  236. << " ifeq ($(TARGET_ARCH),)" << newLine
  237. << " TARGET_ARCH := " << getArchFlags (config) << newLine
  238. << " endif" << newLine
  239. << newLine;
  240. writeCppFlags (out, config);
  241. out << " JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH)";
  242. if (config.isDebug())
  243. out << " -g -ggdb";
  244. if (makefileIsDLL)
  245. out << " -fPIC";
  246. out << " -O" << config.getGCCOptimisationFlag()
  247. << (" " + replacePreprocessorTokens (config, getExtraCompilerFlagsString())).trimEnd()
  248. << " $(CFLAGS)" << newLine;
  249. String cppStandardToUse (getCppStandardString());
  250. if (cppStandardToUse.isEmpty())
  251. cppStandardToUse = "-std=c++11";
  252. out << " JUCE_CXXFLAGS += $(JUCE_CFLAGS) "
  253. << cppStandardToUse << " $(CXXFLAGS)" << newLine;
  254. writeLinkerFlags (out, config);
  255. out << newLine;
  256. String targetName (replacePreprocessorTokens (config, config.getTargetBinaryNameString()));
  257. if (projectType.isStaticLibrary() || projectType.isDynamicLibrary())
  258. targetName = getLibbedFilename (targetName);
  259. else
  260. targetName = targetName.upToLastOccurrenceOf (".", false, false) + makefileTargetSuffix;
  261. out << " TARGET := " << escapeSpaces (targetName) << newLine;
  262. if (projectType.isStaticLibrary())
  263. out << " BLDCMD = $(AR) -rcs $(JUCE_OUTDIR)/$(TARGET) $(OBJECTS)" << newLine;
  264. else
  265. out << " BLDCMD = $(CXX) -o $(JUCE_OUTDIR)/$(TARGET) $(OBJECTS) $(JUCE_LDFLAGS) $(RESOURCES) $(TARGET_ARCH)" << newLine;
  266. out << " CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)" << newLine
  267. << "endif" << newLine
  268. << newLine;
  269. }
  270. void writeObjects (OutputStream& out, const Array<RelativePath>& files) const
  271. {
  272. out << "OBJECTS := \\" << newLine;
  273. for (int i = 0; i < files.size(); ++i)
  274. if (shouldFileBeCompiledByDefault (files.getReference(i)))
  275. out << " $(JUCE_OBJDIR)/" << escapeSpaces (getObjectFileFor (files.getReference(i))) << " \\" << newLine;
  276. out << newLine;
  277. }
  278. void writeMakefile (OutputStream& out, const Array<RelativePath>& files) const
  279. {
  280. out << "# Automatically generated makefile, created by the Projucer" << newLine
  281. << "# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!" << newLine
  282. << newLine;
  283. out << "# build with \"V=1\" for verbose builds" << newLine
  284. << "ifeq ($(V), 1)" << newLine
  285. << "V_AT =" << newLine
  286. << "else" << newLine
  287. << "V_AT = @" << newLine
  288. << "endif" << newLine
  289. << newLine;
  290. out << "# (this disables dependency generation if multiple architectures are set)" << newLine
  291. << "DEPFLAGS := $(if $(word 2, $(TARGET_ARCH)), , -MMD)" << newLine
  292. << newLine;
  293. out << "ifndef STRIP" << newLine
  294. << " STRIP=strip" << newLine
  295. << "endif" << newLine
  296. << newLine;
  297. out << "ifndef AR" << newLine
  298. << " AR=ar" << newLine
  299. << "endif" << newLine
  300. << newLine;
  301. out << "ifndef CONFIG" << newLine
  302. << " CONFIG=" << escapeSpaces (getConfiguration(0)->getName()) << newLine
  303. << "endif" << newLine
  304. << newLine;
  305. for (ConstConfigIterator config (*this); config.next();)
  306. writeConfig (out, *config);
  307. writeObjects (out, files);
  308. out << ".PHONY: clean" << newLine
  309. << newLine;
  310. StringArray packages;
  311. packages.addTokens (getExtraPkgConfigString(), " ", "\"'");
  312. packages.removeEmptyStrings();
  313. bool useLinuxPackages = (linuxPackages.size() > 0 || packages.size() > 0);
  314. out << "$(JUCE_OUTDIR)/$(TARGET): "
  315. << ((useLinuxPackages) ? "check-pkg-config " : "")
  316. << "$(OBJECTS) $(RESOURCES)" << newLine
  317. << "\t@echo Linking " << projectName << newLine
  318. << "\t-@mkdir -p $(JUCE_BINDIR)" << newLine
  319. << "\t-@mkdir -p $(JUCE_LIBDIR)" << newLine
  320. << "\t-@mkdir -p $(JUCE_OUTDIR)" << newLine
  321. << "\t$(V_AT)$(BLDCMD)" << newLine
  322. << newLine;
  323. if (useLinuxPackages)
  324. {
  325. out << "check-pkg-config:" << newLine
  326. << "\t@command -v pkg-config >/dev/null 2>&1 || "
  327. "{ echo >&2 \"pkg-config not installed. Please, install it.\"; "
  328. "exit 1; }" << newLine
  329. << "\t@pkg-config --print-errors";
  330. for (int i = 0; i < linuxPackages.size(); ++i)
  331. out << " " << linuxPackages[i];
  332. for (int i = 0; i < packages.size(); ++i)
  333. out << " " << packages[i];
  334. out << newLine << newLine;
  335. }
  336. out << "clean:" << newLine
  337. << "\t@echo Cleaning " << projectName << newLine
  338. << "\t$(V_AT)$(CLEANCMD)" << newLine
  339. << newLine;
  340. out << "strip:" << newLine
  341. << "\t@echo Stripping " << projectName << newLine
  342. << "\t-@$(STRIP) --strip-unneeded $(JUCE_OUTDIR)/$(TARGET)" << newLine
  343. << newLine;
  344. for (int i = 0; i < files.size(); ++i)
  345. {
  346. if (shouldFileBeCompiledByDefault (files.getReference(i)))
  347. {
  348. jassert (files.getReference(i).getRoot() == RelativePath::buildTargetFolder);
  349. out << "$(JUCE_OBJDIR)/" << escapeSpaces (getObjectFileFor (files.getReference(i)))
  350. << ": " << escapeSpaces (files.getReference(i).toUnixStyle()) << newLine
  351. << "\t-@mkdir -p $(JUCE_OBJDIR)" << newLine
  352. << "\t@echo \"Compiling " << files.getReference(i).getFileName() << "\"" << newLine
  353. << (files.getReference(i).hasFileExtension ("c;s;S") ? "\t$(V_AT)$(CC) $(JUCE_CFLAGS) -o \"$@\" -c \"$<\""
  354. : "\t$(V_AT)$(CXX) $(JUCE_CXXFLAGS) -o \"$@\" -c \"$<\"")
  355. << newLine << newLine;
  356. }
  357. }
  358. out << "-include $(OBJECTS:%.o=%.d)" << newLine;
  359. }
  360. String getArchFlags (const BuildConfiguration& config) const
  361. {
  362. if (const MakeBuildConfiguration* makeConfig = dynamic_cast<const MakeBuildConfiguration*> (&config))
  363. if (! makeConfig->getArchitectureTypeVar().isVoid())
  364. return makeConfig->getArchitectureTypeVar();
  365. return "-march=native";
  366. }
  367. String getObjectFileFor (const RelativePath& file) const
  368. {
  369. return file.getFileNameWithoutExtension()
  370. + "_" + String::toHexString (file.toUnixStyle().hashCode()) + ".o";
  371. }
  372. void initialiseDependencyPathValues()
  373. {
  374. vst3Path.referTo (Value (new DependencyPathValueSource (getSetting (Ids::vst3Folder),
  375. Ids::vst3Path,
  376. TargetOS::linux)));
  377. }
  378. JUCE_DECLARE_NON_COPYABLE (MakefileProjectExporter)
  379. };