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.

395 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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 <iostream>
  19. #include "juce_AppConfig.h"
  20. #include "../../juce_amalgamated.h"
  21. //==============================================================================
  22. static bool matchesWildcard (const String& filename, const StringArray& wildcards)
  23. {
  24. for (int i = wildcards.size(); --i >= 0;)
  25. if (filename.matchesWildcard (wildcards[i], true))
  26. return true;
  27. return false;
  28. }
  29. static bool canFileBeReincluded (const File& f)
  30. {
  31. String content (f.loadFileAsString());
  32. for (;;)
  33. {
  34. content = content.trimStart();
  35. if (content.startsWith (T("//")))
  36. content = content.fromFirstOccurrenceOf (T("\n"), false, false);
  37. else if (content.startsWith (T("/*")))
  38. content = content.fromFirstOccurrenceOf (T("*/"), false, false);
  39. else
  40. break;
  41. }
  42. StringArray lines;
  43. lines.addLines (content);
  44. lines.trim();
  45. lines.removeEmptyStrings();
  46. const String l1 (lines[0].removeCharacters (T(" \t")).trim());
  47. const String l2 (lines[1].removeCharacters (T(" \t")).trim());
  48. if (l1.replace (T("#ifndef"), T("#define")) == l2)
  49. return false;
  50. return true;
  51. }
  52. //==============================================================================
  53. static bool parseFile (const File& rootFolder,
  54. const File& newTargetFile,
  55. StringArray& dest,
  56. const File& file,
  57. StringArray& alreadyIncludedFiles,
  58. const StringArray& includesToIgnore,
  59. const StringArray& wildcards,
  60. const bool isOuterFile,
  61. const bool stripUnnecessaryStuff)
  62. {
  63. if (! file.exists())
  64. {
  65. std::cout << "!! ERROR - file doesn't exist!";
  66. return false;
  67. }
  68. String content (file.loadFileAsString());
  69. if (stripUnnecessaryStuff && ! isOuterFile)
  70. {
  71. if (content.startsWith (T("/*")))
  72. content = content.fromFirstOccurrenceOf (T("*/"), false, false).trimStart();
  73. content = content.replace (T("\r\n\r\n\r\n"), T("\r\n\r\n"));
  74. }
  75. StringArray lines;
  76. lines.addLines (content);
  77. while (lines.size() > 0 && lines[0].trim().isEmpty())
  78. lines.remove (0);
  79. for (int i = 0; i < lines.size(); ++i)
  80. {
  81. String line (lines[i]);
  82. if ((! isOuterFile) && line.contains (T("//================================================================")))
  83. line = String::empty;
  84. if (line.trimStart().startsWithChar (T('#'))
  85. && line.removeCharacters (T(" \t")).startsWithIgnoreCase (T("#include\"")))
  86. {
  87. const int endOfInclude = line.indexOfChar (line.indexOfChar (T('\"')) + 1, T('\"')) + 1;
  88. const String lineUpToEndOfInclude (line.substring (0, endOfInclude));
  89. const String lineAfterInclude (line.substring (endOfInclude));
  90. const String filename (line.fromFirstOccurrenceOf (T("\""), false, false)
  91. .upToLastOccurrenceOf (T("\""), false, false));
  92. const File targetFile (file.getSiblingFile (filename));
  93. if (targetFile.exists()
  94. && targetFile.isAChildOf (rootFolder))
  95. {
  96. if (matchesWildcard (filename.replaceCharacter (T('\\'), T('/')), wildcards)
  97. && ! includesToIgnore.contains (targetFile.getFileName()))
  98. {
  99. if (line.containsIgnoreCase (T("FORCE_AMALGAMATOR_INCLUDE"))
  100. || ! alreadyIncludedFiles.contains (targetFile.getFullPathName()))
  101. {
  102. if (! canFileBeReincluded (targetFile))
  103. alreadyIncludedFiles.add (targetFile.getFullPathName());
  104. dest.add (String::empty);
  105. dest.add (T("/********* Start of inlined file: ")
  106. + targetFile.getFileName()
  107. + T(" *********/"));
  108. if (! parseFile (rootFolder, newTargetFile,
  109. dest, targetFile, alreadyIncludedFiles, includesToIgnore,
  110. wildcards, false, stripUnnecessaryStuff))
  111. {
  112. return false;
  113. }
  114. dest.add (T("/********* End of inlined file: ")
  115. + targetFile.getFileName()
  116. + T(" *********/"));
  117. dest.add (String::empty);
  118. line = lineAfterInclude;
  119. }
  120. else
  121. {
  122. if (stripUnnecessaryStuff)
  123. line = String::empty;
  124. else
  125. line = T("/* ") + lineUpToEndOfInclude + T(" */") + lineAfterInclude;
  126. }
  127. }
  128. else
  129. {
  130. line = lineUpToEndOfInclude.upToFirstOccurrenceOf (T("\""), true, false)
  131. + targetFile.getRelativePathFrom (newTargetFile.getParentDirectory())
  132. .replaceCharacter (T('\\'), T('/'))
  133. + T("\"")
  134. + lineAfterInclude;
  135. }
  136. }
  137. }
  138. if (line.trimStart().startsWith (T("/*")))
  139. {
  140. int originalI = i;
  141. String originalLine = line;
  142. for (;;)
  143. {
  144. int end = line.indexOf (T("*/"));
  145. if (end >= 0)
  146. {
  147. line = line.substring (end + 2);
  148. // If our comment appeared just before an assertion, leave it in, as it
  149. // might be useful..
  150. if (lines [i + 1].contains (T("assert"))
  151. || lines [i + 2].contains (T("assert")))
  152. {
  153. i = originalI;
  154. line = originalLine;
  155. }
  156. break;
  157. }
  158. line = lines [++i];
  159. if (i >= lines.size())
  160. break;
  161. }
  162. line = line.trimEnd();
  163. if (line.isEmpty())
  164. continue;
  165. }
  166. line = line.trimEnd();
  167. {
  168. // Turn initial spaces into tabs..
  169. int numIntialSpaces = 0;
  170. int len = line.length();
  171. while (numIntialSpaces < len && line [numIntialSpaces] == ' ')
  172. ++numIntialSpaces;
  173. if (numIntialSpaces > 0)
  174. {
  175. int tabSize = 4;
  176. int numTabs = numIntialSpaces / tabSize;
  177. line = String::repeatedString (T("\t"), numTabs) + line.substring (numTabs * tabSize);
  178. }
  179. if (! line.containsChar (T('"')))
  180. {
  181. // turn large areas of spaces into tabs - this will mess up alignment a bit, but
  182. // it's only the amalgamated file, so doesn't matter...
  183. line = line.replace (T(" "), T("\t"), false);
  184. line = line.replace (T(" "), T("\t"), false);
  185. }
  186. }
  187. dest.add (line);
  188. }
  189. return true;
  190. }
  191. //==============================================================================
  192. static bool munge (const File& templateFile, const File& targetFile, const String& wildcard,
  193. const bool stripUnnecessaryStuff, StringArray& alreadyIncludedFiles,
  194. const StringArray& includesToIgnore)
  195. {
  196. if (! templateFile.existsAsFile())
  197. {
  198. std::cout << " The template file doesn't exist!\n\n";
  199. return false;
  200. }
  201. StringArray lines, wildcards;
  202. wildcards.addTokens (wildcard, T(";,"), T("'\""));
  203. wildcards.trim();
  204. wildcards.removeEmptyStrings();
  205. if (! parseFile (targetFile.getParentDirectory(),
  206. targetFile,
  207. lines, templateFile,
  208. alreadyIncludedFiles,
  209. includesToIgnore,
  210. wildcards,
  211. true, stripUnnecessaryStuff))
  212. {
  213. return false;
  214. }
  215. std::cout << "Building: " << (const char*) targetFile.getFullPathName() << "...\n";
  216. for (int i = 0; i < lines.size() - 2; ++i)
  217. {
  218. if (lines[i].isEmpty() && lines[i + 1].isEmpty())
  219. {
  220. lines.remove (i + 1);
  221. --i;
  222. }
  223. }
  224. MemoryBlock newData, oldData;
  225. const String newText (lines.joinIntoString (T("\n")) + T("\n"));
  226. newData.append ((const char*) newText, (int) strlen ((const char*) newText));
  227. targetFile.loadFileAsData (oldData);
  228. if (oldData == newData)
  229. {
  230. std::cout << "(No need to write - new file is identical)\n";
  231. return true;
  232. }
  233. if (! targetFile.replaceWithData (newData.getData(), newData.getSize()))
  234. {
  235. std::cout << "\n!! ERROR - couldn't write to the target file: "
  236. << (const char*) targetFile.getFullPathName() << "\n\n";
  237. return false;
  238. }
  239. return true;
  240. }
  241. static void findAllFilesIncludedIn (const File& hppTemplate, StringArray& alreadyIncludedFiles)
  242. {
  243. StringArray lines;
  244. lines.addLines (hppTemplate.loadFileAsString());
  245. for (int i = 0; i < lines.size(); ++i)
  246. {
  247. String line (lines[i]);
  248. if (line.removeCharacters (T(" \t")).startsWithIgnoreCase (T("#include\"")))
  249. {
  250. const String filename (line.fromFirstOccurrenceOf (T("\""), false, false)
  251. .upToLastOccurrenceOf (T("\""), false, false));
  252. const File targetFile (hppTemplate.getSiblingFile (filename));
  253. if (! alreadyIncludedFiles.contains (targetFile.getFullPathName()))
  254. {
  255. alreadyIncludedFiles.add (targetFile.getFullPathName());
  256. if (targetFile.getFileName().containsIgnoreCase (T("juce_")) && targetFile.exists())
  257. findAllFilesIncludedIn (targetFile, alreadyIncludedFiles);
  258. }
  259. }
  260. }
  261. }
  262. //==============================================================================
  263. static void mungeJuce (const File& juceFolder)
  264. {
  265. if (! juceFolder.isDirectory())
  266. {
  267. std::cout << " The folder supplied must be the root of your Juce directory!\n\n";
  268. return;
  269. }
  270. const File hppTemplate (juceFolder.getChildFile (T("src/juce_amalgamated_template.h")));
  271. const File cppTemplate (juceFolder.getChildFile (T("src/juce_amalgamated_template.cpp")));
  272. const File hppTarget (juceFolder.getChildFile (T("juce_amalgamated.h")));
  273. const File cppTarget (juceFolder.getChildFile (T("juce_amalgamated.cpp")));
  274. StringArray alreadyIncludedFiles, includesToIgnore;
  275. if (! munge (hppTemplate, hppTarget,
  276. "*.h", true, alreadyIncludedFiles, includesToIgnore))
  277. {
  278. return;
  279. }
  280. findAllFilesIncludedIn (hppTemplate, alreadyIncludedFiles);
  281. includesToIgnore.add (hppTarget.getFileName());
  282. munge (cppTemplate, cppTarget,
  283. "*.cpp;*.c;*.h;*.mm;*.m", true, alreadyIncludedFiles,
  284. includesToIgnore);
  285. }
  286. //==============================================================================
  287. int main (int argc, char* argv[])
  288. {
  289. // If you're running a command-line app, you need to initialise juce manually
  290. // before calling any Juce functionality..
  291. initialiseJuce_NonGUI();
  292. std::cout << "\n*** The C++ Amalgamator! Written for Juce - www.rawmaterialsoftware.com\n";
  293. if (argc == 4)
  294. {
  295. const File templateFile (File::getCurrentWorkingDirectory().getChildFile (String (argv[1]).unquoted()));
  296. const File targetFile (File::getCurrentWorkingDirectory().getChildFile (String (argv[2]).unquoted()));
  297. const String wildcard (String (argv[3]).unquoted());
  298. StringArray alreadyIncludedFiles, includesToIgnore;
  299. munge (templateFile, targetFile, wildcard, false, alreadyIncludedFiles, includesToIgnore);
  300. }
  301. else if (argc == 2)
  302. {
  303. const File juceFolder (File::getCurrentWorkingDirectory().getChildFile (String (argv[1]).unquoted()));
  304. mungeJuce (juceFolder);
  305. }
  306. else
  307. {
  308. std::cout << " Usage: amalgamator TemplateFile TargetFile \"FileToReplaceWildcard\"\n\n";
  309. " amalgamator will run through a C++ file and replace any\n"
  310. " #include statements with the contents of the file they refer to.\n"
  311. " It'll only do this for files that are within the same parent\n"
  312. " directory as the target file, and will ignore include statements\n"
  313. " that use '<>' instead of quotes. It'll also only include a file once,\n"
  314. " ignoring any repeated instances of it.\n\n"
  315. " The wildcard lets you specify what kind of files will be replaced, so\n"
  316. " \"*.cpp;*.h\" would replace only includes that reference a .cpp or .h file.\n\n"
  317. " Or: just run 'amalgamator YourJuceDirectory' to rebuild the juce files.";
  318. }
  319. std::cout << "\n";
  320. return 0;
  321. }