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 "juce_AppConfig.h"
  19. #include "../../juce_amalgamated.h"
  20. //==============================================================================
  21. static const char* newLine = "\n";
  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. static int64 calculateStreamHashCode (InputStream& in)
  53. {
  54. int64 t = 0;
  55. const int bufferSize = 4096;
  56. HeapBlock <uint8> buffer;
  57. buffer.malloc (bufferSize);
  58. for (;;)
  59. {
  60. const int num = in.read (buffer, bufferSize);
  61. if (num <= 0)
  62. break;
  63. for (int i = 0; i < num; ++i)
  64. t = t * 65599 + buffer[i];
  65. }
  66. return t;
  67. }
  68. static int64 calculateFileHashCode (const File& file)
  69. {
  70. ScopedPointer <FileInputStream> stream (file.createInputStream());
  71. return stream != 0 ? calculateStreamHashCode (*stream) : 0;
  72. }
  73. //==============================================================================
  74. static bool parseFile (const File& rootFolder,
  75. const File& newTargetFile,
  76. OutputStream& dest,
  77. const File& file,
  78. StringArray& alreadyIncludedFiles,
  79. const StringArray& includesToIgnore,
  80. const StringArray& wildcards,
  81. const bool isOuterFile)
  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: " << (const char*) 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 (T("//================================================================")))
  101. line = String::empty;
  102. if (trimmed.startsWithChar (T('#'))
  103. && trimmed.removeCharacters (T(" \t")).startsWithIgnoreCase (T("#include\"")))
  104. {
  105. const int endOfInclude = line.indexOfChar (line.indexOfChar (T('\"')) + 1, T('\"')) + 1;
  106. const String lineUpToEndOfInclude (line.substring (0, endOfInclude));
  107. const String lineAfterInclude (line.substring (endOfInclude));
  108. const String filename (line.fromFirstOccurrenceOf (T("\""), false, false)
  109. .upToLastOccurrenceOf (T("\""), false, false));
  110. const File targetFile (file.getSiblingFile (filename));
  111. if (targetFile.exists() && targetFile.isAChildOf (rootFolder))
  112. {
  113. if (matchesWildcard (filename.replaceCharacter (T('\\'), T('/')), wildcards)
  114. && ! includesToIgnore.contains (targetFile.getFileName()))
  115. {
  116. if (line.containsIgnoreCase (T("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))
  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 (T("\""), true, false)
  139. + targetFile.getRelativePathFrom (newTargetFile.getParentDirectory())
  140. .replaceCharacter (T('\\'), T('/'))
  141. + T("\"")
  142. + lineAfterInclude;
  143. }
  144. }
  145. }
  146. if (trimmed.startsWith (T("/*")) && (i > 10 || ! isOuterFile))
  147. {
  148. int originalI = i;
  149. String originalLine = line;
  150. for (;;)
  151. {
  152. int end = line.indexOf (T("*/"));
  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 (T("assert"))
  159. || lines [i + 2].contains (T("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("\t"), numTabs) + line.substring (numTabs * tabSize);
  186. }
  187. if (! line.containsChar (T('"')))
  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(" "), T("\t"), false);
  192. line = line.replace (T(" "), T("\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, T(";,"), T("'\""));
  212. wildcards.trim();
  213. wildcards.removeEmptyStrings();
  214. std::cout << "Building: " << (const char*) 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. << (const char*) 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))
  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. << (const char*) 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(" \t")).startsWithIgnoreCase (T("#include\"")))
  255. {
  256. const String filename (line.fromFirstOccurrenceOf (T("\""), false, false)
  257. .upToLastOccurrenceOf (T("\""), 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 (T("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 (T("amalgamation/juce_amalgamated_template.h")));
  277. const File cppTemplate (juceFolder.getChildFile (T("amalgamation/juce_amalgamated_template.cpp")));
  278. const File hppTarget (juceFolder.getChildFile (T("juce_amalgamated.h")));
  279. const File cppTarget (juceFolder.getChildFile (T("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. // If you're running a command-line app, you need to initialise juce manually
  293. // before calling any Juce functionality..
  294. initialiseJuce_NonGUI();
  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. }