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.

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