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.

576 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_ProjectExporter.h"
  19. #include "jucer_ProjectSaver.h"
  20. #include "jucer_ProjectExport_Make.h"
  21. #include "jucer_ProjectExport_MSVC.h"
  22. #include "jucer_ProjectExport_XCode.h"
  23. #include "jucer_ProjectExport_Android.h"
  24. #include "jucer_ProjectExport_CodeBlocks.h"
  25. //==============================================================================
  26. StringArray ProjectExporter::getExporterNames()
  27. {
  28. StringArray s;
  29. s.add (XCodeProjectExporter::getNameMac());
  30. s.add (XCodeProjectExporter::getNameiOS());
  31. s.add (MSVCProjectExporterVC2005::getName());
  32. s.add (MSVCProjectExporterVC2008::getName());
  33. s.add (MSVCProjectExporterVC2010::getName());
  34. s.add (MSVCProjectExporterVC2012::getName());
  35. s.add (MakefileProjectExporter::getNameLinux());
  36. s.add (AndroidProjectExporter::getNameAndroid());
  37. s.add (CodeBlocksProjectExporter::getNameCodeBlocks());
  38. return s;
  39. }
  40. String ProjectExporter::getCurrentPlatformExporterName()
  41. {
  42. #if JUCE_MAC
  43. return XCodeProjectExporter::getNameMac();
  44. #elif JUCE_WINDOWS
  45. return MSVCProjectExporterVC2010::getName();
  46. #elif JUCE_LINUX
  47. return MakefileProjectExporter::getNameLinux();
  48. #else
  49. #error // huh?
  50. #endif
  51. }
  52. ProjectExporter* ProjectExporter::createNewExporter (Project& project, const int index)
  53. {
  54. ProjectExporter* exp = nullptr;
  55. switch (index)
  56. {
  57. case 0: exp = new XCodeProjectExporter (project, ValueTree (XCodeProjectExporter ::getValueTreeTypeName (false)), false); break;
  58. case 1: exp = new XCodeProjectExporter (project, ValueTree (XCodeProjectExporter ::getValueTreeTypeName (true)), true); break;
  59. case 2: exp = new MSVCProjectExporterVC2005 (project, ValueTree (MSVCProjectExporterVC2005::getValueTreeTypeName())); break;
  60. case 3: exp = new MSVCProjectExporterVC2008 (project, ValueTree (MSVCProjectExporterVC2008::getValueTreeTypeName())); break;
  61. case 4: exp = new MSVCProjectExporterVC2010 (project, ValueTree (MSVCProjectExporterVC2010::getValueTreeTypeName())); break;
  62. case 5: exp = new MSVCProjectExporterVC2012 (project, ValueTree (MSVCProjectExporterVC2012::getValueTreeTypeName())); break;
  63. case 6: exp = new MakefileProjectExporter (project, ValueTree (MakefileProjectExporter ::getValueTreeTypeName())); break;
  64. case 7: exp = new AndroidProjectExporter (project, ValueTree (AndroidProjectExporter ::getValueTreeTypeName())); break;
  65. case 8: exp = new CodeBlocksProjectExporter (project, ValueTree (CodeBlocksProjectExporter::getValueTreeTypeName())); break;
  66. default: jassertfalse; return 0;
  67. }
  68. File juceFolder (ModuleList::getLocalModulesFolder (&project));
  69. File target (exp->getTargetFolder());
  70. if (FileHelpers::shouldPathsBeRelative (juceFolder.getFullPathName(), project.getFile().getFullPathName()))
  71. exp->getJuceFolderValue() = FileHelpers::getRelativePathFrom (juceFolder, project.getFile().getParentDirectory());
  72. else
  73. exp->getJuceFolderValue() = juceFolder.getFullPathName();
  74. exp->createDefaultConfigs();
  75. return exp;
  76. }
  77. ProjectExporter* ProjectExporter::createNewExporter (Project& project, const String& name)
  78. {
  79. return createNewExporter (project, getExporterNames().indexOf (name));
  80. }
  81. ProjectExporter* ProjectExporter::createExporter (Project& project, const ValueTree& settings)
  82. {
  83. ProjectExporter* exp = MSVCProjectExporterVC2005::createForSettings (project, settings);
  84. if (exp == nullptr) exp = MSVCProjectExporterVC2008::createForSettings (project, settings);
  85. if (exp == nullptr) exp = MSVCProjectExporterVC2010::createForSettings (project, settings);
  86. if (exp == nullptr) exp = MSVCProjectExporterVC2012::createForSettings (project, settings);
  87. if (exp == nullptr) exp = XCodeProjectExporter ::createForSettings (project, settings);
  88. if (exp == nullptr) exp = MakefileProjectExporter ::createForSettings (project, settings);
  89. if (exp == nullptr) exp = AndroidProjectExporter ::createForSettings (project, settings);
  90. if (exp == nullptr) exp = CodeBlocksProjectExporter::createForSettings (project, settings);
  91. jassert (exp != nullptr);
  92. return exp;
  93. }
  94. bool ProjectExporter::canProjectBeLaunched (Project* project)
  95. {
  96. if (project != nullptr)
  97. {
  98. const char* types[] =
  99. {
  100. #if JUCE_MAC
  101. XCodeProjectExporter::getValueTreeTypeName (false),
  102. XCodeProjectExporter::getValueTreeTypeName (true),
  103. #elif JUCE_WINDOWS
  104. MSVCProjectExporterVC2005::getValueTreeTypeName(),
  105. MSVCProjectExporterVC2008::getValueTreeTypeName(),
  106. MSVCProjectExporterVC2010::getValueTreeTypeName(),
  107. MSVCProjectExporterVC2012::getValueTreeTypeName(),
  108. #elif JUCE_LINUX
  109. // (this doesn't currently launch.. not really sure what it would do on linux)
  110. //MakefileProjectExporter::getValueTreeTypeName(),
  111. #endif
  112. nullptr
  113. };
  114. for (const char** type = types; *type != nullptr; ++type)
  115. if (project->getExporters().getChildWithName (*type).isValid())
  116. return true;
  117. }
  118. return false;
  119. }
  120. //==============================================================================
  121. ProjectExporter::ProjectExporter (Project& project_, const ValueTree& settings_)
  122. : xcodeIsBundle (false),
  123. xcodeCreatePList (false),
  124. xcodeCanUseDwarf (true),
  125. makefileIsDLL (false),
  126. msvcIsDLL (false),
  127. msvcIsWindowsSubsystem (true),
  128. settings (settings_),
  129. project (project_),
  130. projectType (project_.getProjectType()),
  131. projectName (project_.getTitle()),
  132. projectFolder (project_.getFile().getParentDirectory()),
  133. modulesGroup (nullptr)
  134. {
  135. }
  136. ProjectExporter::~ProjectExporter()
  137. {
  138. }
  139. File ProjectExporter::getTargetFolder() const
  140. {
  141. return project.resolveFilename (getTargetLocationString());
  142. }
  143. RelativePath ProjectExporter::getJucePathFromProjectFolder() const
  144. {
  145. return RelativePath (getJuceFolderString(), RelativePath::projectFolder);
  146. }
  147. RelativePath ProjectExporter::getJucePathFromTargetFolder() const
  148. {
  149. return rebaseFromProjectFolderToBuildTarget (getJucePathFromProjectFolder());
  150. }
  151. RelativePath ProjectExporter::rebaseFromProjectFolderToBuildTarget (const RelativePath& path) const
  152. {
  153. return path.rebased (project.getFile().getParentDirectory(), getTargetFolder(), RelativePath::buildTargetFolder);
  154. }
  155. bool ProjectExporter::shouldFileBeCompiledByDefault (const RelativePath& file) const
  156. {
  157. return file.hasFileExtension ("cpp;cc;c;cxx");
  158. }
  159. void ProjectExporter::createPropertyEditors (PropertyListBuilder& props)
  160. {
  161. props.add (new TextPropertyComponent (getTargetLocationValue(), "Target Project Folder", 1024, false),
  162. "The location of the folder in which the " + name + " project will be created. "
  163. "This path can be absolute, but it's much more sensible to make it relative to the jucer project directory.");
  164. props.add (new TextPropertyComponent (getJuceFolderValue(), "Local JUCE folder", 1024, false),
  165. "The location of the Juce library folder that the " + name + " project will use to when compiling. "
  166. "This can be an absolute path, or relative to the jucer project folder, but it must be valid on the "
  167. "filesystem of the machine you use to actually do the compiling.");
  168. OwnedArray<LibraryModule> modules;
  169. ModuleList moduleList;
  170. moduleList.rescan (ModuleList::getDefaultModulesFolder (&project));
  171. project.createRequiredModules (moduleList, modules);
  172. for (int i = 0; i < modules.size(); ++i)
  173. modules.getUnchecked(i)->createPropertyEditors (*this, props);
  174. props.add (new TextPropertyComponent (getExporterPreprocessorDefs(), "Extra Preprocessor Definitions", 32768, true),
  175. "Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, "
  176. "or new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
  177. props.add (new TextPropertyComponent (getExtraCompilerFlags(), "Extra compiler flags", 2048, true),
  178. "Extra command-line flags to be passed to the compiler. This string can contain references to preprocessor definitions in the "
  179. "form ${NAME_OF_DEFINITION}, which will be replaced with their values.");
  180. props.add (new TextPropertyComponent (getExtraLinkerFlags(), "Extra linker flags", 2048, true),
  181. "Extra command-line flags to be passed to the linker. You might want to use this for adding additional libraries. "
  182. "This string can contain references to preprocessor definitions in the form ${NAME_OF_VALUE}, which will be replaced with their values.");
  183. props.add (new TextPropertyComponent (getExternalLibraries(), "External libraries to link", 2048, true),
  184. "Additional libraries to link (one per line). You should not add any platform specific decoration to these names. "
  185. "This string can contain references to preprocessor definitions in the form ${NAME_OF_VALUE}, which will be replaced with their values.");
  186. {
  187. OwnedArray<Project::Item> images;
  188. project.findAllImageItems (images);
  189. StringArray choices;
  190. Array<var> ids;
  191. choices.add ("<None>");
  192. ids.add (var::null);
  193. choices.add (String::empty);
  194. ids.add (var::null);
  195. for (int i = 0; i < images.size(); ++i)
  196. {
  197. choices.add (images.getUnchecked(i)->getName());
  198. ids.add (images.getUnchecked(i)->getID());
  199. }
  200. props.add (new ChoicePropertyComponent (getSmallIconImageItemID(), "Icon (small)", choices, ids),
  201. "Sets an icon to use for the executable.");
  202. props.add (new ChoicePropertyComponent (getBigIconImageItemID(), "Icon (large)", choices, ids),
  203. "Sets an icon to use for the executable.");
  204. }
  205. createExporterProperties (props);
  206. props.add (new TextPropertyComponent (getUserNotes(), "Notes", 32768, true),
  207. "Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
  208. }
  209. StringPairArray ProjectExporter::getAllPreprocessorDefs (const ProjectExporter::BuildConfiguration& config) const
  210. {
  211. StringPairArray defs (mergePreprocessorDefs (config.getAllPreprocessorDefs(),
  212. parsePreprocessorDefs (getExporterPreprocessorDefsString())));
  213. defs.set (getExporterIdentifierMacro(), "1");
  214. return defs;
  215. }
  216. StringPairArray ProjectExporter::getAllPreprocessorDefs() const
  217. {
  218. StringPairArray defs (mergePreprocessorDefs (project.getPreprocessorDefs(),
  219. parsePreprocessorDefs (getExporterPreprocessorDefsString())));
  220. defs.set (getExporterIdentifierMacro(), "1");
  221. return defs;
  222. }
  223. String ProjectExporter::replacePreprocessorTokens (const ProjectExporter::BuildConfiguration& config, const String& sourceString) const
  224. {
  225. return replacePreprocessorDefs (getAllPreprocessorDefs (config), sourceString);
  226. }
  227. void ProjectExporter::copyMainGroupFromProject()
  228. {
  229. jassert (itemGroups.size() == 0);
  230. itemGroups.add (project.getMainGroup().createCopy());
  231. }
  232. Project::Item& ProjectExporter::getModulesGroup()
  233. {
  234. if (modulesGroup == nullptr)
  235. {
  236. jassert (itemGroups.size() > 0); // must call copyMainGroupFromProject before this.
  237. itemGroups.add (Project::Item::createGroup (project, "Juce Modules", "__modulesgroup__"));
  238. modulesGroup = &(itemGroups.getReference (itemGroups.size() - 1));
  239. }
  240. return *modulesGroup;
  241. }
  242. void ProjectExporter::addToExtraSearchPaths (const RelativePath& pathFromProjectFolder)
  243. {
  244. RelativePath localPath (rebaseFromProjectFolderToBuildTarget (pathFromProjectFolder));
  245. const String path (isVisualStudio() ? localPath.toWindowsStyle() : localPath.toUnixStyle());
  246. extraSearchPaths.addIfNotAlreadyThere (path, false);
  247. }
  248. //==============================================================================
  249. const Identifier ProjectExporter::configurations ("CONFIGURATIONS");
  250. const Identifier ProjectExporter::configuration ("CONFIGURATION");
  251. ValueTree ProjectExporter::getConfigurations() const
  252. {
  253. return settings.getChildWithName (configurations);
  254. }
  255. int ProjectExporter::getNumConfigurations() const
  256. {
  257. return getConfigurations().getNumChildren();
  258. }
  259. ProjectExporter::BuildConfiguration::Ptr ProjectExporter::getConfiguration (int index) const
  260. {
  261. return createBuildConfig (getConfigurations().getChild (index));
  262. }
  263. bool ProjectExporter::hasConfigurationNamed (const String& nameToFind) const
  264. {
  265. const ValueTree configs (getConfigurations());
  266. for (int i = configs.getNumChildren(); --i >= 0;)
  267. if (configs.getChild(i) [Ids::name].toString() == nameToFind)
  268. return true;
  269. return false;
  270. }
  271. String ProjectExporter::getUniqueConfigName (String nm) const
  272. {
  273. String nameRoot (nm);
  274. while (CharacterFunctions::isDigit (nameRoot.getLastCharacter()))
  275. nameRoot = nameRoot.dropLastCharacters (1);
  276. nameRoot = nameRoot.trim();
  277. int suffix = 2;
  278. while (hasConfigurationNamed (name))
  279. nm = nameRoot + " " + String (suffix++);
  280. return nm;
  281. }
  282. void ProjectExporter::addNewConfiguration (const BuildConfiguration* configToCopy)
  283. {
  284. const String configName (getUniqueConfigName (configToCopy != nullptr ? configToCopy->config [Ids::name].toString()
  285. : "New Build Configuration"));
  286. ValueTree configs (getConfigurations());
  287. if (! configs.isValid())
  288. {
  289. settings.addChild (ValueTree (configurations), 0, project.getUndoManagerFor (settings));
  290. configs = getConfigurations();
  291. }
  292. ValueTree newConfig (configuration);
  293. if (configToCopy != nullptr)
  294. newConfig = configToCopy->config.createCopy();
  295. newConfig.setProperty (Ids::name, configName, 0);
  296. configs.addChild (newConfig, -1, project.getUndoManagerFor (configs));
  297. }
  298. void ProjectExporter::BuildConfiguration::removeFromExporter()
  299. {
  300. ValueTree configs (config.getParent());
  301. configs.removeChild (config, project.getUndoManagerFor (configs));
  302. }
  303. void ProjectExporter::createDefaultConfigs()
  304. {
  305. settings.getOrCreateChildWithName (configurations, nullptr);
  306. for (int i = 0; i < 2; ++i)
  307. {
  308. addNewConfiguration (nullptr);
  309. BuildConfiguration::Ptr config (getConfiguration (i));
  310. const bool debugConfig = i == 0;
  311. config->getNameValue() = debugConfig ? "Debug" : "Release";
  312. config->isDebugValue() = debugConfig;
  313. config->getOptimisationLevel() = debugConfig ? optimisationOff : optimiseMinSize;
  314. config->getTargetBinaryName() = project.getProjectFilenameRoot();
  315. }
  316. }
  317. Image ProjectExporter::getBigIcon() const
  318. {
  319. return project.getMainGroup().findItemWithID (settings [Ids::bigIcon]).loadAsImageFile();
  320. }
  321. Image ProjectExporter::getSmallIcon() const
  322. {
  323. return project.getMainGroup().findItemWithID (settings [Ids::smallIcon]).loadAsImageFile();
  324. }
  325. Image ProjectExporter::getBestIconForSize (int size, bool returnNullIfNothingBigEnough) const
  326. {
  327. Image im;
  328. const Image im1 (getSmallIcon());
  329. const Image im2 (getBigIcon());
  330. if (im1.isValid() && im2.isValid())
  331. {
  332. if (im1.getWidth() >= size && im2.getWidth() >= size)
  333. im = im1.getWidth() < im2.getWidth() ? im1 : im2;
  334. else if (im1.getWidth() >= size)
  335. im = im1;
  336. else if (im2.getWidth() >= size)
  337. im = im2;
  338. else
  339. return Image::null;
  340. }
  341. else
  342. {
  343. im = im1.isValid() ? im1 : im2;
  344. }
  345. if (returnNullIfNothingBigEnough && im.getWidth() < size && im.getHeight() < size)
  346. return Image::null;
  347. return rescaleImageForIcon (im, size);
  348. }
  349. Image ProjectExporter::rescaleImageForIcon (Image im, const int size)
  350. {
  351. im = SoftwareImageType().convert (im);
  352. if (size == im.getWidth() && size == im.getHeight())
  353. return im;
  354. // (scale it down in stages for better resampling)
  355. while (im.getWidth() > 2 * size && im.getHeight() > 2 * size)
  356. im = im.rescaled (im.getWidth() / 2,
  357. im.getHeight() / 2);
  358. Image newIm (Image::ARGB, size, size, true, SoftwareImageType());
  359. Graphics g (newIm);
  360. g.drawImageWithin (im, 0, 0, size, size,
  361. RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false);
  362. return newIm;
  363. }
  364. //==============================================================================
  365. ProjectExporter::ConfigIterator::ConfigIterator (ProjectExporter& exporter_)
  366. : index (-1), exporter (exporter_)
  367. {
  368. }
  369. bool ProjectExporter::ConfigIterator::next()
  370. {
  371. if (++index >= exporter.getNumConfigurations())
  372. return false;
  373. config = exporter.getConfiguration (index);
  374. return true;
  375. }
  376. ProjectExporter::ConstConfigIterator::ConstConfigIterator (const ProjectExporter& exporter_)
  377. : index (-1), exporter (exporter_)
  378. {
  379. }
  380. bool ProjectExporter::ConstConfigIterator::next()
  381. {
  382. if (++index >= exporter.getNumConfigurations())
  383. return false;
  384. config = exporter.getConfiguration (index);
  385. return true;
  386. }
  387. //==============================================================================
  388. ProjectExporter::BuildConfiguration::BuildConfiguration (Project& project_, const ValueTree& configNode)
  389. : config (configNode), project (project_)
  390. {
  391. }
  392. ProjectExporter::BuildConfiguration::~BuildConfiguration()
  393. {
  394. }
  395. String ProjectExporter::BuildConfiguration::getGCCOptimisationFlag() const
  396. {
  397. switch (getOptimisationLevelInt())
  398. {
  399. case optimiseMaxSpeed: return "3";
  400. case optimiseMinSize: return "s";
  401. default: return "0";
  402. }
  403. }
  404. void ProjectExporter::BuildConfiguration::createPropertyEditors (PropertyListBuilder& props)
  405. {
  406. props.add (new TextPropertyComponent (getNameValue(), "Name", 96, false),
  407. "The name of this configuration.");
  408. props.add (new BooleanPropertyComponent (isDebugValue(), "Debug mode", "Debugging enabled"),
  409. "If enabled, this means that the configuration should be built with debug synbols.");
  410. const char* optimisationLevels[] = { "No optimisation", "Minimise size", "Maximise speed", 0 };
  411. const int optimisationLevelValues[] = { optimisationOff, optimiseMinSize, optimiseMaxSpeed, 0 };
  412. props.add (new ChoicePropertyComponent (getOptimisationLevel(), "Optimisation",
  413. StringArray (optimisationLevels), Array<var> (optimisationLevelValues)),
  414. "The optimisation level for this configuration");
  415. props.add (new TextPropertyComponent (getTargetBinaryName(), "Binary name", 256, false),
  416. "The filename to use for the destination binary executable file. If you don't add a suffix to this name, "
  417. "a suitable platform-specific suffix will be added automatically.");
  418. props.add (new TextPropertyComponent (getTargetBinaryRelativePath(), "Binary location", 1024, false),
  419. "The folder in which the finished binary should be placed. Leave this blank to cause the binary to be placed "
  420. "in its default location in the build folder.");
  421. props.addSearchPathProperty (getHeaderSearchPathValue(), "Header search paths", "Extra header search paths.");
  422. props.addSearchPathProperty (getLibrarySearchPathValue(), "Extra library search paths", "Extra library search paths.");
  423. props.add (new TextPropertyComponent (getBuildConfigPreprocessorDefs(), "Preprocessor definitions", 32768, true),
  424. "Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, or "
  425. "new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
  426. createConfigProperties (props);
  427. props.add (new TextPropertyComponent (getUserNotes(), "Notes", 32768, true),
  428. "Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
  429. }
  430. StringPairArray ProjectExporter::BuildConfiguration::getAllPreprocessorDefs() const
  431. {
  432. return mergePreprocessorDefs (project.getPreprocessorDefs(),
  433. parsePreprocessorDefs (getBuildConfigPreprocessorDefsString()));
  434. }
  435. StringArray ProjectExporter::BuildConfiguration::getHeaderSearchPaths() const
  436. {
  437. return getSearchPathsFromString (getHeaderSearchPathString());
  438. }
  439. StringArray ProjectExporter::BuildConfiguration::getLibrarySearchPaths() const
  440. {
  441. return getSearchPathsFromString (getLibrarySearchPathString());
  442. }
  443. String ProjectExporter::BuildConfiguration::getGCCLibraryPathFlags() const
  444. {
  445. String s;
  446. const StringArray libraryPaths (getLibrarySearchPaths());
  447. for (int i = 0; i < libraryPaths.size(); ++i)
  448. s << " -L" << addQuotesIfContainsSpaces (libraryPaths[i]);
  449. return s;
  450. }
  451. String ProjectExporter::getExternalLibraryFlags (const BuildConfiguration& config) const
  452. {
  453. StringArray libraries;
  454. libraries.addTokens (getExternalLibrariesString(), ";\n", "\"'");
  455. libraries.removeEmptyStrings (true);
  456. if (libraries.size() != 0)
  457. return replacePreprocessorTokens (config, "-l" + libraries.joinIntoString (" -l")).trim();
  458. return String::empty;
  459. }