2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-9 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../JuceLibraryCode/JuceHeader.h"
29 //==============================================================================
30 static bool matchesWildcard (const String
& filename
, const StringArray
& wildcards
)
32 for (int i
= wildcards
.size(); --i
>= 0;)
33 if (filename
.matchesWildcard (wildcards
[i
], true))
39 static bool canFileBeReincluded (const File
& f
)
41 String
content (f
.loadFileAsString());
45 content
= content
.trimStart();
47 if (content
.startsWith ("//"))
48 content
= content
.fromFirstOccurrenceOf ("\n", false, false);
49 else if (content
.startsWith ("/*"))
50 content
= content
.fromFirstOccurrenceOf ("*/", false, false);
56 lines
.addLines (content
);
58 lines
.removeEmptyStrings();
60 const String
l1 (lines
[0].removeCharacters (" \t").trim());
61 const String
l2 (lines
[1].removeCharacters (" \t").trim());
63 if (l1
.replace ("#ifndef", "#define") == l2
)
69 static int64
calculateStreamHashCode (InputStream
& in
)
73 const int bufferSize
= 4096;
74 HeapBlock
<uint8
> buffer
;
75 buffer
.malloc (bufferSize
);
79 const int num
= in
.read (buffer
, bufferSize
);
84 for (int i
= 0; i
< num
; ++i
)
85 t
= t
* 65599 + buffer
[i
];
91 static int64
calculateFileHashCode (const File
& file
)
93 ScopedPointer
<FileInputStream
> stream (file
.createInputStream());
94 return stream
!= 0 ? calculateStreamHashCode (*stream
) : 0;
98 //==============================================================================
99 static bool parseFile (const File
& rootFolder
,
100 const File
& newTargetFile
,
103 StringArray
& alreadyIncludedFiles
,
104 const StringArray
& includesToIgnore
,
105 const StringArray
& wildcards
,
107 bool stripCommentBlocks
)
111 std::cout
<< "!! ERROR - file doesn't exist!";
116 lines
.addLines (file
.loadFileAsString());
118 if (lines
.size() == 0)
120 std::cout
<< "!! ERROR - input file was empty: " << file
.getFullPathName();
124 bool lastLineWasBlank
= true;
126 for (int i
= 0; i
< lines
.size(); ++i
)
128 String
line (lines
[i
]);
129 String
trimmed (line
.trimStart());
131 if ((! isOuterFile
) && trimmed
.startsWith ("//================================================================"))
132 line
= String::empty
;
134 if (trimmed
.startsWithChar ('#')
135 && trimmed
.removeCharacters (" \t").startsWithIgnoreCase ("#include\""))
137 const int endOfInclude
= line
.indexOfChar (line
.indexOfChar ('\"') + 1, '\"') + 1;
138 const String
lineUpToEndOfInclude (line
.substring (0, endOfInclude
));
139 const String
lineAfterInclude (line
.substring (endOfInclude
));
141 const String
filename (line
.fromFirstOccurrenceOf ("\"", false, false)
142 .upToLastOccurrenceOf ("\"", false, false));
143 const File
targetFile (file
.getSiblingFile (filename
));
145 if (targetFile
.exists() && targetFile
.isAChildOf (rootFolder
))
147 if (matchesWildcard (filename
.replaceCharacter ('\\', '/'), wildcards
)
148 && ! includesToIgnore
.contains (targetFile
.getFileName()))
150 if (line
.containsIgnoreCase ("FORCE_AMALGAMATOR_INCLUDE")
151 || ! alreadyIncludedFiles
.contains (targetFile
.getFullPathName()))
153 if (! canFileBeReincluded (targetFile
))
154 alreadyIncludedFiles
.add (targetFile
.getFullPathName());
156 dest
<< newLine
<< "/*** Start of inlined file: " << targetFile
.getFileName() << " ***/" << newLine
;
158 if (! parseFile (rootFolder
, newTargetFile
,
159 dest
, targetFile
, alreadyIncludedFiles
, includesToIgnore
,
160 wildcards
, false, stripCommentBlocks
))
165 dest
<< "/*** End of inlined file: " << targetFile
.getFileName() << " ***/" << newLine
<< newLine
;
167 line
= lineAfterInclude
;
171 line
= String::empty
;
176 line
= lineUpToEndOfInclude
.upToFirstOccurrenceOf ("\"", true, false)
177 + targetFile
.getRelativePathFrom (newTargetFile
.getParentDirectory())
178 .replaceCharacter ('\\', '/')
185 if ((stripCommentBlocks
|| i
== 0) && trimmed
.startsWith ("/*") && (i
> 10 || ! isOuterFile
))
188 String originalLine
= line
;
192 int end
= line
.indexOf ("*/");
196 line
= line
.substring (end
+ 2);
198 // If our comment appeared just before an assertion, leave it in, as it
200 if (lines
[i
+ 1].contains ("assert")
201 || lines
[i
+ 2].contains ("assert"))
212 if (i
>= lines
.size())
216 line
= line
.trimEnd();
221 line
= line
.trimEnd();
224 // Turn initial spaces into tabs..
225 int numIntialSpaces
= 0;
226 int len
= line
.length();
227 while (numIntialSpaces
< len
&& line
[numIntialSpaces
] == ' ')
230 if (numIntialSpaces
> 0)
233 int numTabs
= numIntialSpaces
/ tabSize
;
234 line
= String::repeatedString ("\t", numTabs
) + line
.substring (numTabs
* tabSize
);
237 if (! line
.containsChar ('"'))
239 // turn large areas of spaces into tabs - this will mess up alignment a bit, but
240 // it's only the amalgamated file, so doesn't matter...
241 line
= line
.replace (" ", "\t", false);
242 line
= line
.replace (" ", "\t", false);
246 if (line
.isNotEmpty() || ! lastLineWasBlank
)
247 dest
<< line
<< newLine
;
249 lastLineWasBlank
= line
.isEmpty();
255 //==============================================================================
256 static bool munge (const File
& templateFile
, const File
& targetFile
, const String
& wildcard
,
257 StringArray
& alreadyIncludedFiles
, const StringArray
& includesToIgnore
)
259 if (! templateFile
.existsAsFile())
261 std::cout
<< " The template file doesn't exist!\n\n";
265 StringArray wildcards
;
266 wildcards
.addTokens (wildcard
, ";,", "'\"");
268 wildcards
.removeEmptyStrings();
270 std::cout
<< "Building: " << targetFile
.getFullPathName() << "...\n";
272 TemporaryFile
temp (targetFile
);
273 ScopedPointer
<FileOutputStream
> out (temp
.getFile().createOutputStream (1024 * 128));
277 std::cout
<< "\n!! ERROR - couldn't write to the target file: "
278 << temp
.getFile().getFullPathName() << "\n\n";
282 out
->setNewLineString ("\n");
284 if (! parseFile (targetFile
.getParentDirectory(),
287 alreadyIncludedFiles
,
297 if (calculateFileHashCode (targetFile
) == calculateFileHashCode (temp
.getFile()))
299 std::cout
<< " -- No need to write - new file is identical\n";
303 if (! temp
.overwriteTargetFileWithTemporary())
305 std::cout
<< "\n!! ERROR - couldn't write to the target file: "
306 << targetFile
.getFullPathName() << "\n\n";
313 static void findAllFilesIncludedIn (const File
& hppTemplate
, StringArray
& alreadyIncludedFiles
)
316 lines
.addLines (hppTemplate
.loadFileAsString());
318 for (int i
= 0; i
< lines
.size(); ++i
)
320 String
line (lines
[i
]);
322 if (line
.removeCharacters (" \t").startsWithIgnoreCase ("#include\""))
324 const String
filename (line
.fromFirstOccurrenceOf ("\"", false, false)
325 .upToLastOccurrenceOf ("\"", false, false));
326 const File
targetFile (hppTemplate
.getSiblingFile (filename
));
328 if (! alreadyIncludedFiles
.contains (targetFile
.getFullPathName()))
330 alreadyIncludedFiles
.add (targetFile
.getFullPathName());
332 if (targetFile
.getFileName().containsIgnoreCase ("juce_") && targetFile
.exists())
333 findAllFilesIncludedIn (targetFile
, alreadyIncludedFiles
);
339 //==============================================================================
340 static void mungeJuce (const File
& juceFolder
)
342 if (! juceFolder
.isDirectory())
344 std::cout
<< " The folder supplied must be the root of your Juce directory!\n\n";
348 const File
hppTemplate (juceFolder
.getChildFile ("amalgamation/juce_amalgamated_template.h"));
349 const File
cppTemplate (juceFolder
.getChildFile ("amalgamation/juce_amalgamated_template.cpp"));
351 const File
hppTarget (juceFolder
.getChildFile ("juce_amalgamated.h"));
352 const File
cppTarget (juceFolder
.getChildFile ("juce_amalgamated.cpp"));
354 StringArray alreadyIncludedFiles
, includesToIgnore
;
356 if (! munge (hppTemplate
, hppTarget
, "*.h", alreadyIncludedFiles
, includesToIgnore
))
361 findAllFilesIncludedIn (hppTemplate
, alreadyIncludedFiles
);
362 includesToIgnore
.add (hppTarget
.getFileName());
364 munge (cppTemplate
, cppTarget
, "*.cpp;*.c;*.h;*.mm;*.m", alreadyIncludedFiles
, includesToIgnore
);
367 //==============================================================================
368 int main (int argc
, char* argv
[])
370 std::cout
<< "\n*** The C++ Amalgamator! Written for Juce - www.rawmaterialsoftware.com\n";
374 const File
templateFile (File::getCurrentWorkingDirectory().getChildFile (String (argv
[1]).unquoted()));
375 const File
targetFile (File::getCurrentWorkingDirectory().getChildFile (String (argv
[2]).unquoted()));
376 const String
wildcard (String (argv
[3]).unquoted());
377 StringArray alreadyIncludedFiles
, includesToIgnore
;
379 munge (templateFile
, targetFile
, wildcard
, alreadyIncludedFiles
, includesToIgnore
);
383 const File
juceFolder (File::getCurrentWorkingDirectory().getChildFile (String (argv
[1]).unquoted()));
384 mungeJuce (juceFolder
);
388 std::cout
<< " Usage: amalgamator TemplateFile TargetFile \"FileToReplaceWildcard\"\n\n"
389 " amalgamator will run through a C++ file and replace any\n"
390 " #include statements with the contents of the file they refer to.\n"
391 " It'll only do this for files that are within the same parent\n"
392 " directory as the target file, and will ignore include statements\n"
393 " that use '<>' instead of quotes. It'll also only include a file once,\n"
394 " ignoring any repeated instances of it.\n\n"
395 " The wildcard lets you specify what kind of files will be replaced, so\n"
396 " \"*.cpp;*.h\" would replace only includes that reference a .cpp or .h file.\n\n"
397 " Or: just run 'amalgamator YourJuceDirectory' to rebuild the juce files.";