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.

407 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 (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. 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. const bool isOuterFile)
  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: " << (const char*) 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 (T("//================================================================")))
  100. line = String::empty;
  101. if (trimmed.startsWithChar (T('#'))
  102. && trimmed.removeCharacters (T(" \t")).startsWithIgnoreCase (T("#include\"")))
  103. {
  104. const int endOfInclude = line.indexOfChar (line.indexOfChar (T('\"')) + 1, T('\"')) + 1;
  105. const String lineUpToEndOfInclude (line.substring (0, endOfInclude));
  106. const String lineAfterInclude (line.substring (endOfInclude));
  107. const String filename (line.fromFirstOccurrenceOf (T("\""), false, false)
  108. .upToLastOccurrenceOf (T("\""), false, false));
  109. const File targetFile (file.getSiblingFile (filename));
  110. if (targetFile.exists() && targetFile.isAChildOf (rootFolder))
  111. {
  112. if (matchesWildcard (filename.replaceCharacter (T('\\'), T('/')), wildcards)
  113. && ! includesToIgnore.contains (targetFile.getFileName()))
  114. {
  115. if (line.containsIgnoreCase (T("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))
  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 (T("\""), true, false)
  138. + targetFile.getRelativePathFrom (newTargetFile.getParentDirectory())
  139. .replaceCharacter (T('\\'), T('/'))
  140. + T("\"")
  141. + lineAfterInclude;
  142. }
  143. }
  144. }
  145. if (trimmed.startsWith (T("/*")) && (i > 10 || ! isOuterFile))
  146. {
  147. int originalI = i;
  148. String originalLine = line;
  149. for (;;)
  150. {
  151. int end = line.indexOf (T("*/"));
  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 (T("assert"))
  158. || lines [i + 2].contains (T("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("\t"), numTabs) + line.substring (numTabs * tabSize);
  185. }
  186. if (! line.containsChar (T('"')))
  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(" "), T("\t"), false);
  191. line = line.replace (T(" "), T("\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, T(";,"), T("'\""));
  211. wildcards.trim();
  212. wildcards.removeEmptyStrings();
  213. std::cout << "Building: " << (const char*) 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. << (const char*) temp.getFile().getFullPathName() << "\n\n";
  220. return false;
  221. }
  222. if (! parseFile (targetFile.getParentDirectory(),
  223. targetFile,
  224. *out, templateFile,
  225. alreadyIncludedFiles,
  226. includesToIgnore,
  227. wildcards,
  228. true))
  229. {
  230. return false;
  231. }
  232. out = 0;
  233. if (calculateFileHashCode (targetFile) == calculateFileHashCode (temp.getFile()))
  234. {
  235. std::cout << " -- No need to write - new file is identical\n";
  236. return true;
  237. }
  238. if (! temp.overwriteTargetFileWithTemporary())
  239. {
  240. std::cout << "\n!! ERROR - couldn't write to the target file: "
  241. << (const char*) targetFile.getFullPathName() << "\n\n";
  242. return false;
  243. }
  244. return true;
  245. }
  246. static void findAllFilesIncludedIn (const File& hppTemplate, StringArray& alreadyIncludedFiles)
  247. {
  248. StringArray lines;
  249. lines.addLines (hppTemplate.loadFileAsString());
  250. for (int i = 0; i < lines.size(); ++i)
  251. {
  252. String line (lines[i]);
  253. if (line.removeCharacters (T(" \t")).startsWithIgnoreCase (T("#include\"")))
  254. {
  255. const String filename (line.fromFirstOccurrenceOf (T("\""), false, false)
  256. .upToLastOccurrenceOf (T("\""), false, false));
  257. const File targetFile (hppTemplate.getSiblingFile (filename));
  258. if (! alreadyIncludedFiles.contains (targetFile.getFullPathName()))
  259. {
  260. alreadyIncludedFiles.add (targetFile.getFullPathName());
  261. if (targetFile.getFileName().containsIgnoreCase (T("juce_")) && targetFile.exists())
  262. findAllFilesIncludedIn (targetFile, alreadyIncludedFiles);
  263. }
  264. }
  265. }
  266. }
  267. //==============================================================================
  268. static void mungeJuce (const File& juceFolder)
  269. {
  270. if (! juceFolder.isDirectory())
  271. {
  272. std::cout << " The folder supplied must be the root of your Juce directory!\n\n";
  273. return;
  274. }
  275. const File hppTemplate (juceFolder.getChildFile (T("amalgamation/juce_amalgamated_template.h")));
  276. const File cppTemplate (juceFolder.getChildFile (T("amalgamation/juce_amalgamated_template.cpp")));
  277. const File hppTarget (juceFolder.getChildFile (T("juce_amalgamated.h")));
  278. const File cppTarget (juceFolder.getChildFile (T("juce_amalgamated.cpp")));
  279. StringArray alreadyIncludedFiles, includesToIgnore;
  280. if (! munge (hppTemplate, hppTarget, "*.h", alreadyIncludedFiles, includesToIgnore))
  281. {
  282. return;
  283. }
  284. findAllFilesIncludedIn (hppTemplate, alreadyIncludedFiles);
  285. includesToIgnore.add (hppTarget.getFileName());
  286. munge (cppTemplate, cppTarget, "*.cpp;*.c;*.h;*.mm;*.m", alreadyIncludedFiles, includesToIgnore);
  287. }
  288. //==============================================================================
  289. int main (int argc, char* argv[])
  290. {
  291. // This object makes sure that Juce is initialised and shut down correctly
  292. // for the scope of this function call. Make sure this declaration is the
  293. // first statement of this function.
  294. const ScopedJuceInitialiser_NonGUI juceSystemInitialiser;
  295. std::cout << "\n*** The C++ Amalgamator! Written for Juce - www.rawmaterialsoftware.com\n";
  296. if (argc == 4)
  297. {
  298. const File templateFile (File::getCurrentWorkingDirectory().getChildFile (String (argv[1]).unquoted()));
  299. const File targetFile (File::getCurrentWorkingDirectory().getChildFile (String (argv[2]).unquoted()));
  300. const String wildcard (String (argv[3]).unquoted());
  301. StringArray alreadyIncludedFiles, includesToIgnore;
  302. munge (templateFile, targetFile, wildcard, alreadyIncludedFiles, includesToIgnore);
  303. }
  304. else if (argc == 2)
  305. {
  306. const File juceFolder (File::getCurrentWorkingDirectory().getChildFile (String (argv[1]).unquoted()));
  307. mungeJuce (juceFolder);
  308. }
  309. else
  310. {
  311. std::cout << " Usage: amalgamator TemplateFile TargetFile \"FileToReplaceWildcard\"\n\n";
  312. " amalgamator will run through a C++ file and replace any\n"
  313. " #include statements with the contents of the file they refer to.\n"
  314. " It'll only do this for files that are within the same parent\n"
  315. " directory as the target file, and will ignore include statements\n"
  316. " that use '<>' instead of quotes. It'll also only include a file once,\n"
  317. " ignoring any repeated instances of it.\n\n"
  318. " The wildcard lets you specify what kind of files will be replaced, so\n"
  319. " \"*.cpp;*.h\" would replace only includes that reference a .cpp or .h file.\n\n"
  320. " Or: just run 'amalgamator YourJuceDirectory' to rebuild the juce files.";
  321. }
  322. std::cout << "\n";
  323. return 0;
  324. }