generator.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. 
  2. #include "generator.h"
  3. using namespace dsr;
  4. static uint64_t checksum(const ReadableString& text) {
  5. uint64_t a = 0x8C2A03D4;
  6. uint64_t b = 0xF42B1583;
  7. uint64_t c = 0xA6815E74;
  8. uint64_t d = 0;
  9. for (int64_t i = 0; i < string_length(text); i++) {
  10. a = (b * c + ((i * 3756 + 2654) & 58043)) & 0xFFFFFFFF;
  11. b = (231 + text[i] * (a & 154) + c * 867 + 28294061) & 0xFFFFFFFF;
  12. c = (a ^ b ^ (text[i] * 1543217521)) & 0xFFFFFFFF;
  13. d = d ^ (a << 32) ^ b ^ (c << 16);
  14. }
  15. return d;
  16. }
  17. static uint64_t checksum(const Buffer& buffer) {
  18. SafePointer<uint8_t> data = buffer_getSafeData<uint8_t>(buffer, "checksum input buffer");
  19. uint64_t a = 0x8C2A03D4;
  20. uint64_t b = 0xF42B1583;
  21. uint64_t c = 0xA6815E74;
  22. uint64_t d = 0;
  23. for (int64_t i = 0; i < buffer_getSize(buffer); i++) {
  24. a = (b * c + ((i * 3756 + 2654) & 58043)) & 0xFFFFFFFF;
  25. b = (231 + data[i] * (a & 154) + c * 867 + 28294061) & 0xFFFFFFFF;
  26. c = (a ^ b ^ (data[i] * 1543217521)) & 0xFFFFFFFF;
  27. d = d ^ (a << 32) ^ b ^ (c << 16);
  28. }
  29. return d;
  30. }
  31. static int64_t findDependency(ProjectContext &context, const ReadableString& findPath);
  32. static void resolveConnection(Connection &connection);
  33. static void resolveDependency(Dependency &dependency);
  34. static String findSourceFile(const ReadableString& headerPath, bool acceptC, bool acceptCpp);
  35. static void flushToken(List<String> &target, String &currentToken);
  36. static void tokenize(List<String> &target, const ReadableString& line);
  37. static int64_t findDependency(ProjectContext &context, const ReadableString& findPath) {
  38. for (int64_t d = 0; d < context.dependencies.length(); d++) {
  39. if (string_match(context.dependencies[d].path, findPath)) {
  40. return d;
  41. }
  42. }
  43. return -1;
  44. }
  45. static void resolveConnection(ProjectContext &context, Connection &connection) {
  46. connection.dependencyIndex = findDependency(context, connection.path);
  47. }
  48. static void resolveDependency(ProjectContext &context, Dependency &dependency) {
  49. for (int64_t l = 0; l < dependency.links.length(); l++) {
  50. resolveConnection(context, dependency.links[l]);
  51. }
  52. for (int64_t i = 0; i < dependency.includes.length(); i++) {
  53. resolveConnection(context, dependency.includes[i]);
  54. }
  55. }
  56. void resolveDependencies(ProjectContext &context) {
  57. for (int64_t d = 0; d < context.dependencies.length(); d++) {
  58. resolveDependency(context, context.dependencies[d]);
  59. }
  60. }
  61. static String findSourceFile(const ReadableString& headerPath, bool acceptC, bool acceptCpp) {
  62. if (file_hasExtension(headerPath)) {
  63. ReadableString extensionlessPath = file_getExtensionless(headerPath);
  64. String cPath = extensionlessPath + U".c";
  65. String cppPath = extensionlessPath + U".cpp";
  66. if (acceptC && file_getEntryType(cPath) == EntryType::File) {
  67. return cPath;
  68. } else if (acceptCpp && file_getEntryType(cppPath) == EntryType::File) {
  69. return cppPath;
  70. }
  71. }
  72. return U"";
  73. }
  74. static void flushToken(List<String> &target, String &currentToken) {
  75. if (string_length(currentToken) > 0) {
  76. target.push(currentToken);
  77. currentToken = U"";
  78. }
  79. }
  80. static void tokenize(List<String> &target, const ReadableString& line) {
  81. String currentToken;
  82. for (int64_t i = 0; i < string_length(line); i++) {
  83. DsrChar c = line[i];
  84. DsrChar nextC = line[i + 1];
  85. if (c == U'#' && nextC == U'#') {
  86. // Appending tokens using ##
  87. i++;
  88. } else if (c == U'#' || c == U'(' || c == U')' || c == U'[' || c == U']' || c == U'{' || c == U'}') {
  89. // Atomic token of a single character
  90. flushToken(target, currentToken);
  91. string_appendChar(currentToken, c);
  92. flushToken(target, currentToken);
  93. } else if (c == U' ' || c == U'\t') {
  94. // Whitespace
  95. flushToken(target, currentToken);
  96. } else {
  97. string_appendChar(currentToken, c);
  98. }
  99. }
  100. flushToken(target, currentToken);
  101. }
  102. // When CACHED_ANALYSIS is enabled, files will only be analyzed once per session, by remembering them from previous projects.
  103. // If features that require a different type of analysis per project are implemented, this can easily be turned off.
  104. #define CACHED_ANALYSIS
  105. #ifdef CACHED_ANALYSIS
  106. // Remembering previous results from analyzing the same files.
  107. List<Dependency> analysisCache;
  108. #endif
  109. void analyzeFile(Dependency &result, const ReadableString& absolutePath, Extension extension) {
  110. #ifdef CACHED_ANALYSIS
  111. // Check if the file has already been analyzed.
  112. for (int c = 0; c < analysisCache.length(); c++) {
  113. if (string_match(analysisCache[c].path, absolutePath)) {
  114. // Clone all the results to keep projects separate in memory for safety.
  115. result = analysisCache[c];
  116. return;
  117. }
  118. }
  119. #endif
  120. // Get the file's binary content.
  121. Buffer fileBuffer = file_loadBuffer(absolutePath);
  122. // Get the checksum
  123. result.contentChecksum = checksum(fileBuffer);
  124. if (extension == Extension::H || extension == Extension::Hpp) {
  125. // The current file is a header, so look for an implementation with the corresponding name.
  126. String sourcePath = findSourceFile(absolutePath, extension == Extension::H, true);
  127. // If found:
  128. if (string_length(sourcePath) > 0) {
  129. // Remember that anything using the header will have to link with the implementation.
  130. result.links.pushConstruct(sourcePath);
  131. }
  132. }
  133. // Interpret the file's content.
  134. String sourceCode = string_loadFromMemory(fileBuffer);
  135. String parentFolder = file_getRelativeParentFolder(absolutePath);
  136. List<String> tokens;
  137. bool continuingLine = false;
  138. int64_t lineNumber = 0;
  139. string_split_callback(sourceCode, U'\n', true, [&result, &parentFolder, &tokens, &continuingLine, &lineNumber](ReadableString line) {
  140. lineNumber++;
  141. if (line[0] == U'#' || continuingLine) {
  142. tokenize(tokens, line);
  143. // Continuing pre-processing line using \ at the end.
  144. continuingLine = line[string_length(line) - 1] == U'\\';
  145. } else {
  146. continuingLine = false;
  147. }
  148. if (!continuingLine && tokens.length() > 0) {
  149. if (tokens.length() >= 3) {
  150. if (string_match(tokens[1], U"include")) {
  151. if (tokens[2][0] == U'\"') {
  152. String relativePath = string_unmangleQuote(tokens[2]);
  153. String absolutePath = file_getTheoreticalAbsolutePath(relativePath, parentFolder, LOCAL_PATH_SYNTAX);
  154. result.includes.pushConstruct(absolutePath, lineNumber);
  155. }
  156. }
  157. }
  158. tokens.clear();
  159. }
  160. });
  161. }
  162. void analyzeFromFile(ProjectContext &context, const ReadableString& absolutePath) {
  163. if (findDependency(context, absolutePath) != -1) {
  164. // Already analyzed the current entry. Abort to prevent duplicate dependencies.
  165. return;
  166. }
  167. Extension extension = extensionFromString(file_getExtension(absolutePath));
  168. if (extension != Extension::Unknown) {
  169. // Create a new dependency for the file.
  170. int64_t parentIndex = context.dependencies.length();
  171. context.dependencies.push(Dependency(absolutePath, extension));
  172. // Summarize the file's content.
  173. analyzeFile(context.dependencies[parentIndex], absolutePath, extension);
  174. // Continue analyzing recursively into the file's dependencies.
  175. for (int64_t i = 0; i < context.dependencies[parentIndex].includes.length(); i++) {
  176. analyzeFromFile(context, context.dependencies[parentIndex].includes[i].path);
  177. }
  178. for (int64_t l = 0; l < context.dependencies[parentIndex].links.length(); l++) {
  179. analyzeFromFile(context, context.dependencies[parentIndex].links[l].path);
  180. }
  181. }
  182. }
  183. static void debugPrintDependencyList(const List<Connection> &connnections, const ReadableString verb) {
  184. for (int64_t c = 0; c < connnections.length(); c++) {
  185. int64_t lineNumber = connnections[c].lineNumber;
  186. if (lineNumber != -1) {
  187. printText(U" @", lineNumber, U"\t");
  188. } else {
  189. printText(U" \t");
  190. }
  191. printText(U" ", verb, U" ", file_getPathlessName(connnections[c].path), U"\n");
  192. }
  193. }
  194. void printDependencies(ProjectContext &context) {
  195. for (int64_t d = 0; d < context.dependencies.length(); d++) {
  196. printText(U"* ", file_getPathlessName(context.dependencies[d].path), U"\n");
  197. debugPrintDependencyList(context.dependencies[d].includes, U"including");
  198. debugPrintDependencyList(context.dependencies[d].links, U"linking");
  199. }
  200. }
  201. static void script_printMessage(String &output, ScriptLanguage language, const ReadableString message) {
  202. if (language == ScriptLanguage::Batch) {
  203. string_append(output, U"echo ", message, U"\n");
  204. } else if (language == ScriptLanguage::Bash) {
  205. string_append(output, U"echo ", message, U"\n");
  206. }
  207. }
  208. static void traverserHeaderChecksums(ProjectContext &context, uint64_t &target, int64_t dependencyIndex) {
  209. // Use checksums from headers
  210. for (int64_t h = 0; h < context.dependencies[dependencyIndex].includes.length(); h++) {
  211. int64_t includedIndex = context.dependencies[dependencyIndex].includes[h].dependencyIndex;
  212. if (!context.dependencies[includedIndex].visited) {
  213. //printText(U" traverserHeaderChecksums(context, ", includedIndex, U") ", context.dependencies[includedIndex].path, "\n");
  214. // Bitwise exclusive or is both order independent and entropy preserving for non-repeated content.
  215. target = target ^ context.dependencies[includedIndex].contentChecksum;
  216. // Just have to make sure that the same checksum is not used twice.
  217. context.dependencies[includedIndex].visited = true;
  218. // Use checksums from headers recursively
  219. traverserHeaderChecksums(context, target, includedIndex);
  220. }
  221. }
  222. }
  223. static uint64_t getCombinedChecksum(ProjectContext &context, int64_t dependencyIndex) {
  224. //printText(U"getCombinedChecksum(context, ", dependencyIndex, U") ", context.dependencies[dependencyIndex].path, "\n");
  225. for (int64_t d = 0; d < context.dependencies.length(); d++) {
  226. context.dependencies[d].visited = false;
  227. }
  228. context.dependencies[dependencyIndex].visited = true;
  229. uint64_t result = context.dependencies[dependencyIndex].contentChecksum;
  230. traverserHeaderChecksums(context, result, dependencyIndex);
  231. return result;
  232. }
  233. static int64_t findObject(SessionContext &source, uint64_t identityChecksum) {
  234. for (int64_t o = 0; o < source.sourceObjects.length(); o++) {
  235. if (source.sourceObjects[o].identityChecksum == identityChecksum) {
  236. return o;
  237. }
  238. }
  239. return -1;
  240. }
  241. void gatherBuildInstructions(SessionContext &output, ProjectContext &context, Machine &settings, ReadableString programPath) {
  242. // The compiler is often a global alias, so the user must supply either an alias or an absolute path.
  243. ReadableString compilerName = getFlag(settings, U"Compiler", U"g++"); // Assume g++ as the compiler if not specified.
  244. ReadableString compileFrom = getFlag(settings, U"CompileFrom", U"");
  245. // Check if the build system was asked to run the compiler from a specific folder.
  246. bool changePath = (string_length(compileFrom) > 0);
  247. if (changePath) {
  248. printText(U"Using ", compilerName, " as the compiler executed from ", compileFrom, ".\n");
  249. } else {
  250. printText(U"Using ", compilerName, " as the compiler from the current directory.\n");
  251. }
  252. // TODO: Warn if -DNDEBUG, -DDEBUG, or optimization levels are given directly.
  253. // Using the variables instead is both more flexible by accepting input arguments
  254. // and keeping the same format to better reuse compiled objects.
  255. if (getFlagAsInteger(settings, U"Debug")) {
  256. printText(U"Building with debug mode.\n");
  257. settings.compilerFlags.push(U"-DDEBUG");
  258. } else {
  259. printText(U"Building with release mode.\n");
  260. settings.compilerFlags.push(U"-DNDEBUG");
  261. }
  262. if (getFlagAsInteger(settings, U"StaticRuntime")) {
  263. if (getFlagAsInteger(settings, U"Windows")) {
  264. printText(U"Building with static runtime. Your application's binary will be bigger but can run without needing any installer.\n");
  265. settings.compilerFlags.push(U"-static");
  266. settings.compilerFlags.push(U"-static-libgcc");
  267. settings.compilerFlags.push(U"-static-libstdc++");
  268. settings.linkerFlags.push(U"-static");
  269. settings.linkerFlags.push(U"-static-libgcc");
  270. settings.linkerFlags.push(U"-static-libstdc++");
  271. } else {
  272. printText(U"The target platform does not support static linking of runtime. But don't worry about bundling any runtimes, because it comes with most of the Posix compliant operating systems.\n");
  273. }
  274. } else {
  275. printText(U"Building with dynamic runtime. Don't forget to bundle the C and C++ runtimes for systems that don't have it pre-installed.\n");
  276. }
  277. ReadableString optimizationLevel = getFlag(settings, U"Optimization", U"2");
  278. printText(U"Building with optimization level ", optimizationLevel, U".\n");
  279. settings.compilerFlags.push(string_combine(U"-O", optimizationLevel));
  280. // Convert lists of linker and compiler flags into strings.
  281. // TODO: Give a warning if two contradictory flags are used, such as optimization levels and language versions.
  282. // TODO: Make sure that no spaces are inside of the flags, because that can mess up detection of pre-existing and contradictory arguments.
  283. // TODO: Use groups of compiler flags, so that they can be generated in the last step.
  284. // This would allow calling the compiler directly when given a folder path for temporary files instead of a script path.
  285. String generatedCompilerFlags;
  286. for (int64_t i = 0; i < settings.compilerFlags.length(); i++) {
  287. string_append(generatedCompilerFlags, " ", settings.compilerFlags[i]);
  288. }
  289. String linkerFlags;
  290. for (int64_t i = 0; i < settings.linkerFlags.length(); i++) {
  291. string_append(linkerFlags, " -l", settings.linkerFlags[i]);
  292. }
  293. printText(U"Generating build instructions for ", programPath, U" using settings:\n");
  294. printText(U" Compiler flags:", generatedCompilerFlags, U"\n");
  295. printText(U" Linker flags:", linkerFlags, U"\n");
  296. for (int64_t v = 0; v < settings.variables.length(); v++) {
  297. printText(U" * ", settings.variables[v].key, U" = ", settings.variables[v].value);
  298. if (settings.variables[v].inherited) {
  299. printText(U" (inherited input)");
  300. }
  301. printText(U"\n");
  302. }
  303. printText(U"Listing source files to compile in the current session.\n");
  304. // The current project's global indices to objects shared between all projects being built during the session.
  305. List<int64_t> sourceObjectIndices;
  306. bool hasSourceCode = false;
  307. for (int64_t d = 0; d < context.dependencies.length(); d++) {
  308. Extension extension = context.dependencies[d].extension;
  309. if (extension == Extension::C || extension == Extension::Cpp) {
  310. // Dependency paths are already absolute from the recursive search.
  311. String sourcePath = context.dependencies[d].path;
  312. String identity = string_combine(sourcePath, generatedCompilerFlags);
  313. uint64_t identityChecksum = checksum(identity);
  314. int64_t previousIndex = findObject(output, identityChecksum);
  315. if (previousIndex == -1) {
  316. // Content checksums were created while scanning for source code, so now we just combine each source file's content checksum with all its headers to get the combined checksum.
  317. // The combined checksum represents the state after all headers are included recursively and given as input for compilation unit generating an object.
  318. uint64_t combinedChecksum = getCombinedChecksum(context, d);
  319. String objectPath = file_combinePaths(output.tempPath, string_combine(U"dfpsr_", identityChecksum, U"_", combinedChecksum, U".o"));
  320. sourceObjectIndices.push(output.sourceObjects.length());
  321. output.sourceObjects.pushConstruct(identityChecksum, combinedChecksum, sourcePath, objectPath, generatedCompilerFlags, compilerName, compileFrom);
  322. } else {
  323. // Link to this pre-existing source file.
  324. sourceObjectIndices.push(previousIndex);
  325. }
  326. hasSourceCode = true;
  327. }
  328. }
  329. if (hasSourceCode) {
  330. printText(U"Listing target executable ", programPath, " in the current session.\n");
  331. bool executeResult = getFlagAsInteger(settings, U"Supressed") == 0;
  332. output.linkerSteps.pushConstruct(compilerName, compileFrom, programPath, settings.linkerFlags, sourceObjectIndices, executeResult);
  333. } else {
  334. printText(U"Filed to find any source code to compile when building ", programPath, U".\n");
  335. }
  336. }
  337. static ScriptLanguage identifyLanguage(const ReadableString &filename) {
  338. String scriptExtension = string_upperCase(file_getExtension(filename));
  339. if (string_match(scriptExtension, U"BAT")) {
  340. return ScriptLanguage::Batch;
  341. } else if (string_match(scriptExtension, U"SH")) {
  342. return ScriptLanguage::Bash;
  343. } else {
  344. throwError(U"Could not identify the scripting language of ", filename, U". Use *.bat or *.sh.\n");
  345. return ScriptLanguage::Unknown;
  346. }
  347. }
  348. void setCompilationFolder(String &generatedCode, ScriptLanguage language, String &currentPath, const ReadableString &newPath) {
  349. if (!string_match(currentPath, newPath)) {
  350. if (string_length(currentPath) > 0) {
  351. if (language == ScriptLanguage::Batch) {
  352. string_append(generatedCode, "popd\n");
  353. } else if (language == ScriptLanguage::Bash) {
  354. string_append(generatedCode, U")\n");
  355. }
  356. }
  357. if (string_length(newPath) > 0) {
  358. if (language == ScriptLanguage::Batch) {
  359. string_append(generatedCode, "pushd ", newPath, "\n");
  360. } else if (language == ScriptLanguage::Bash) {
  361. string_append(generatedCode, U"(cd ", newPath, ";\n");
  362. }
  363. }
  364. }
  365. }
  366. void generateCompilationScript(SessionContext &input, const ReadableString &scriptPath) {
  367. printText(U"Generating build script\n");
  368. String generatedCode;
  369. ScriptLanguage language = identifyLanguage(scriptPath);
  370. if (language == ScriptLanguage::Batch) {
  371. string_append(generatedCode, U"@echo off\n\n");
  372. } else if (language == ScriptLanguage::Bash) {
  373. string_append(generatedCode, U"#!/bin/bash\n\n");
  374. }
  375. // Keep track of the current path, so that it only changes when needed.
  376. String currentPath;
  377. // Generate code for compiling source code into objects.
  378. printText(U"Generating code for compiling ", input.sourceObjects.length(), U" objects.\n");
  379. for (int64_t o = 0; o < input.sourceObjects.length(); o++) {
  380. SourceObject *sourceObject = &(input.sourceObjects[o]);
  381. printText(U"\t* ", sourceObject->sourcePath, U"\n");
  382. setCompilationFolder(generatedCode, language, currentPath, sourceObject->compileFrom);
  383. if (language == ScriptLanguage::Batch) {
  384. string_append(generatedCode, U"if exist ", sourceObject->objectPath, U" (\n");
  385. } else if (language == ScriptLanguage::Bash) {
  386. string_append(generatedCode, U"if [ -e \"", sourceObject->objectPath, U"\" ]; then\n");
  387. }
  388. script_printMessage(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  389. if (language == ScriptLanguage::Batch) {
  390. string_append(generatedCode, U") else (\n");
  391. } else if (language == ScriptLanguage::Bash) {
  392. string_append(generatedCode, U"else\n");
  393. }
  394. String compilerFlags = sourceObject->generatedCompilerFlags;
  395. script_printMessage(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U" with ", compilerFlags, U"."));
  396. string_append(generatedCode, sourceObject->compilerName, compilerFlags, U" -c ", sourceObject->sourcePath, U" -o ", sourceObject->objectPath, U"\n");
  397. if (language == ScriptLanguage::Batch) {
  398. string_append(generatedCode, ")\n");
  399. } else if (language == ScriptLanguage::Bash) {
  400. string_append(generatedCode, U"fi\n");
  401. }
  402. }
  403. // Generate code for linking objects into executables.
  404. printText(U"Generating code for linking ", input.linkerSteps.length(), U" executables:\n");
  405. for (int64_t l = 0; l < input.linkerSteps.length(); l++) {
  406. LinkingStep *linkingStep = &(input.linkerSteps[l]);
  407. String programPath = linkingStep->binaryName;
  408. printText(U"\tGenerating code for linking ", programPath, U" of :\n");
  409. setCompilationFolder(generatedCode, language, currentPath, linkingStep->compileFrom);
  410. String linkerFlags;
  411. for (int64_t lib = 0; lib < linkingStep->linkerFlags.length(); lib++) {
  412. String library = linkingStep->linkerFlags[lib];
  413. string_append(linkerFlags, " -l", library);
  414. printText(U"\t\t* ", library, U" library\n");
  415. }
  416. // Generate a list of object paths from indices.
  417. String allObjects;
  418. for (int64_t i = 0; i < linkingStep->sourceObjectIndices.length(); i++) {
  419. int64_t objectIndex = linkingStep->sourceObjectIndices[i];
  420. SourceObject *sourceObject = &(input.sourceObjects[objectIndex]);
  421. if (objectIndex >= 0 || objectIndex < input.sourceObjects.length()) {
  422. printText(U"\t\t* ", sourceObject->sourcePath, U"\n");
  423. string_append(allObjects, U" ", sourceObject->objectPath);
  424. } else {
  425. throwError(U"Object index ", objectIndex, U" is out of bound ", 0, U"..", (input.sourceObjects.length() - 1), U"\n");
  426. }
  427. }
  428. // Generate the code for building.
  429. if (string_length(linkerFlags) > 0) {
  430. script_printMessage(generatedCode, language, string_combine(U"Linking ", programPath, U" with", linkerFlags, U"."));
  431. } else {
  432. script_printMessage(generatedCode, language, string_combine(U"Linking ", programPath, U"."));
  433. }
  434. string_append(generatedCode, linkingStep->compilerName, allObjects, linkerFlags, U" -o ", programPath, U"\n");
  435. if (linkingStep->executeResult) {
  436. script_printMessage(generatedCode, language, string_combine(U"Starting ", programPath));
  437. string_append(generatedCode, programPath, U"\n");
  438. script_printMessage(generatedCode, language, U"The program terminated.");
  439. }
  440. }
  441. setCompilationFolder(generatedCode, language, currentPath, U"");
  442. script_printMessage(generatedCode, language, U"Done building.");
  443. // Save the script.
  444. printText(U"Saving script to ", scriptPath, "\n");
  445. if (language == ScriptLanguage::Batch) {
  446. string_save(scriptPath, generatedCode);
  447. } else if (language == ScriptLanguage::Bash) {
  448. string_save(scriptPath, generatedCode, CharacterEncoding::BOM_UTF8, LineEncoding::Lf);
  449. }
  450. }