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 const char* newLine = "\n";
  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 ("//"))
  35. content = content.fromFirstOccurrenceOf ("\n", false, false);
  36. else if (content.startsWith ("/*"))
  37. content = content.fromFirstOccurrenceOf ("*/", 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").trim());
  46. const String l2 (lines[1].removeCharacters (" \t").trim());
  47. if (l1.replace ("#ifndef", "#define") == l2)
  48. return false;
  49. return true;
  50. }
  51. static int64 calculateStreamHashCode (InputStream& in)
  52. {
  53. int64 t = 0;
  54. const int bufferSize = 4096;
  55. HeapBlock <uint8> buffer;
  56. buffer.malloc (bufferSize);
  57. for (;;)
  58. {
  59. const int num = in.read (buffer, bufferSize);
  60. if (num <= 0)
  61. break;
  62. for (int i = 0; i < num; ++i)
  63. t = t * 65599 + buffer[i];
  64. }
  65. return t;
  66. }
  67. static int64 calculateFileHashCode (const File& file)
  68. {
  69. ScopedPointer <FileInputStream> stream (file.createInputStream());
  70. return stream != 0 ? calculateStreamHashCode (*stream) : 0;
  71. }
  72. //==============================================================================
  73. static bool parseFile (const File& rootFolder,
  74. const File& newTargetFile,
  75. OutputStream& dest,
  76. const File& file,
  77. StringArray& alreadyIncludedFiles,
  78. const StringArray& includesToIgnore,
  79. const StringArray& wildcards,
  80. bool isOuterFile,
  81. bool stripCommentBlocks)
  82. {
  83. if (! file.exists())
  84. {
  85. std::cout << "!! ERROR - file doesn't exist!";
  86. return false;
  87. }
  88. StringArray lines;
  89. lines.addLines (file.loadFileAsString());
  90. if (lines.size() == 0)
  91. {
  92. std::cout << "!! ERROR - input file was empty: " << file.getFullPathName();
  93. return false;
  94. }
  95. bool lastLineWasBlank = true;
  96. for (int i = 0; i < lines.size(); ++i)
  97. {
  98. String line (lines[i]);
  99. String trimmed (line.trimStart());
  100. if ((! isOuterFile) && trimmed.startsWith ("//================================================================"))
  101. line = String::empty;
  102. if (trimmed.startsWithChar ('#')
  103. && trimmed.removeCharacters (" \t").startsWithIgnoreCase ("#include\""))
  104. {
  105. const int endOfInclude = line.indexOfChar (line.indexOfChar ('\"') + 1, '\"') + 1;
  106. const String lineUpToEndOfInclude (line.substring (0, endOfInclude));
  107. const String lineAfterInclude (line.substring (endOfInclude));
  108. const String filename (line.fromFirstOccurrenceOf ("\"", false, false)
  109. .upToLastOccurrenceOf ("\"", false, false));
  110. const File targetFile (file.getSiblingFile (filename));
  111. if (targetFile.exists() && targetFile.isAChildOf (rootFolder))
  112. {
  113. if (matchesWildcard (filename.replaceCharacter ('\\', '/'), wildcards)
  114. && ! includesToIgnore.contains (targetFile.getFileName()))
  115. {
  116. if (line.containsIgnoreCase ("FORCE_AMALGAMATOR_INCLUDE")
  117. || ! alreadyIncludedFiles.contains (targetFile.getFullPathName()))
  118. {
  119. if (! canFileBeReincluded (targetFile))
  120. alreadyIncludedFiles.add (targetFile.getFullPathName());
  121. dest << newLine << "/*** Start of inlined file: " << targetFile.getFileName() << " ***/" << newLine;
  122. if (! parseFile (rootFolder, newTargetFile,
  123. dest, targetFile, alreadyIncludedFiles, includesToIgnore,
  124. wildcards, false, stripCommentBlocks))
  125. {
  126. return false;
  127. }
  128. dest << "/*** End of inlined file: " << targetFile.getFileName() << " ***/" << newLine << newLine;
  129. line = lineAfterInclude;
  130. }
  131. else
  132. {
  133. line = String::empty;
  134. }
  135. }
  136. else
  137. {
  138. line = lineUpToEndOfInclude.upToFirstOccurrenceOf ("\"", true, false)
  139. + targetFile.getRelativePathFrom (newTargetFile.getParentDirectory())
  140. .replaceCharacter ('\\', '/')
  141. + "\""
  142. + lineAfterInclude;
  143. }
  144. }
  145. }
  146. if ((stripCommentBlocks || i == 0) && trimmed.startsWith ("/*") && (i > 10 || ! isOuterFile))
  147. {
  148. int originalI = i;
  149. String originalLine = line;
  150. for (;;)
  151. {
  152. int end = line.indexOf ("*/");
  153. if (end >= 0)
  154. {
  155. line = line.substring (end + 2);
  156. // If our comment appeared just before an assertion, leave it in, as it
  157. // might be useful..
  158. if (lines [i + 1].contains ("assert")
  159. || lines [i + 2].contains ("assert"))
  160. {
  161. i = originalI;
  162. line = originalLine;
  163. }
  164. break;
  165. }
  166. line = lines [++i];
  167. if (i >= lines.size())
  168. break;
  169. }
  170. line = line.trimEnd();
  171. if (line.isEmpty())
  172. continue;
  173. }
  174. line = line.trimEnd();
  175. {
  176. // Turn initial spaces into tabs..
  177. int numIntialSpaces = 0;
  178. int len = line.length();
  179. while (numIntialSpaces < len && line [numIntialSpaces] == ' ')
  180. ++numIntialSpaces;
  181. if (numIntialSpaces > 0)
  182. {
  183. int tabSize = 4;
  184. int numTabs = numIntialSpaces / tabSize;
  185. line = String::repeatedString ("\t", numTabs) + line.substring (numTabs * tabSize);
  186. }
  187. if (! line.containsChar ('"'))
  188. {
  189. // turn large areas of spaces into tabs - this will mess up alignment a bit, but
  190. // it's only the amalgamated file, so doesn't matter...
  191. line = line.replace (" ", "\t", false);
  192. line = line.replace (" ", "\t", false);
  193. }
  194. }
  195. if (line.isNotEmpty() || ! lastLineWasBlank)
  196. dest << line << newLine;
  197. lastLineWasBlank = line.isEmpty();
  198. }
  199. return true;
  200. }
  201. //==============================================================================
  202. static bool munge (const File& templateFile, const File& targetFile, const String& wildcard,
  203. StringArray& alreadyIncludedFiles, const StringArray& includesToIgnore)
  204. {
  205. if (! templateFile.existsAsFile())
  206. {
  207. std::cout << " The template file doesn't exist!\n\n";
  208. return false;
  209. }
  210. StringArray wildcards;
  211. wildcards.addTokens (wildcard, ";,", "'\"");
  212. wildcards.trim();
  213. wildcards.removeEmptyStrings();
  214. std::cout << "Building: " << targetFile.getFullPathName() << "...\n";
  215. TemporaryFile temp (targetFile);
  216. ScopedPointer <FileOutputStream> out (temp.getFile().createOutputStream (1024 * 128));
  217. if (out == 0)
  218. {
  219. std::cout << "\n!! ERROR - couldn't write to the target file: "
  220. << temp.getFile().getFullPathName() << "\n\n";
  221. return false;
  222. }
  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. }