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.

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