generator.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. 
  2. #include "generator.h"
  3. #include "../../../DFPSR/api/timeAPI.h"
  4. using namespace dsr;
  5. // Keep track of the current path, so that it only changes when needed.
  6. String previousPath;
  7. template <bool GENERATE>
  8. static void produce_printMessage(String &generatedCode, ScriptLanguage language, const ReadableString message) {
  9. if (GENERATE) {
  10. if (language == ScriptLanguage::Batch) {
  11. string_append(generatedCode, U"echo ", message, U"\n");
  12. } else if (language == ScriptLanguage::Bash) {
  13. string_append(generatedCode, U"echo ", message, U"\n");
  14. }
  15. } else {
  16. printText(message, U"\n");
  17. }
  18. }
  19. template <bool GENERATE>
  20. static void produce_setCompilationFolder(String &generatedCode, ScriptLanguage language, const ReadableString &newPath) {
  21. if (GENERATE) {
  22. if (!string_match(previousPath, newPath)) {
  23. if (string_length(previousPath) > 0) {
  24. if (language == ScriptLanguage::Batch) {
  25. string_append(generatedCode, U"popd\n");
  26. } else if (language == ScriptLanguage::Bash) {
  27. string_append(generatedCode, U")\n");
  28. }
  29. }
  30. if (string_length(newPath) > 0) {
  31. if (language == ScriptLanguage::Batch) {
  32. string_append(generatedCode, U"pushd ", newPath, U"\n");
  33. } else if (language == ScriptLanguage::Bash) {
  34. string_append(generatedCode, U"(cd ", newPath, U";\n");
  35. }
  36. }
  37. }
  38. previousPath = newPath;
  39. } else {
  40. if (string_length(newPath) > 0) {
  41. if (string_length(previousPath) == 0) {
  42. previousPath = file_getCurrentPath();
  43. }
  44. file_setCurrentPath(newPath);
  45. }
  46. }
  47. }
  48. template <bool GENERATE>
  49. static void produce_resetCompilationFolder(String &generatedCode, ScriptLanguage language) {
  50. if (GENERATE) {
  51. produce_setCompilationFolder<true>(generatedCode, language, U"");
  52. } else {
  53. if (string_length(previousPath) > 0) {
  54. file_setCurrentPath(previousPath);
  55. }
  56. }
  57. }
  58. static bool waitForProcess(const DsrProcess &process) {
  59. while (true) {
  60. DsrProcessStatus status = process_getStatus(process);
  61. if (status == DsrProcessStatus::Completed) {
  62. return true;
  63. } else if (status == DsrProcessStatus::Crashed) {
  64. return false;
  65. } else if (status == DsrProcessStatus::NotStarted) {
  66. return false;
  67. }
  68. time_sleepSeconds(0.001);
  69. }
  70. }
  71. template <bool GENERATE>
  72. static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath, const List<String> &arguments) {
  73. if (GENERATE) {
  74. string_append(generatedCode, programPath);
  75. for (int64_t a = 0; a < arguments.length(); a++) {
  76. // TODO: Check if arguments contain spaces. In batch, adding quote marks might actually send the quote marks as a part of a string, which makes it complicated when default folder names on Windows contain spaces.
  77. string_append(generatedCode, U" ", arguments[a]);
  78. }
  79. string_append(generatedCode, U"\n");
  80. } else {
  81. // Print each external call in the terminal, because there is no script to inspect when not generating.
  82. if (arguments.length() > 0) {
  83. printText(U"Calling ", programPath, U" with");
  84. for (int64_t a = 0; a < arguments.length(); a++) {
  85. printText(U" ", arguments[a]);
  86. }
  87. printText(U"\n");
  88. } else {
  89. printText(U"Calling ", programPath, U"\n");
  90. }
  91. // TODO: How can multiple calls be made to the compiler at the same time and only wait for all before linking?
  92. // Don't want to break control flow from the code generating a serial script, so maybe a waitForAll command before performing any linking.
  93. // Don't want error messages from multiple failed compilations to collide in the same terminal.
  94. if (file_getEntryType(programPath) != EntryType::File) {
  95. throwError(U"Failed to execute ", programPath, U", because the executable file was not found!\n");
  96. } else {
  97. if (!waitForProcess(process_execute(programPath, arguments))) {
  98. throwError(U"Failed to execute ", programPath, U"!\n");
  99. }
  100. }
  101. }
  102. }
  103. template <bool GENERATE>
  104. static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath) {
  105. produce_callProgram<GENERATE>(generatedCode, language, programPath, List<String>());
  106. }
  107. template <bool GENERATE>
  108. void produce(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) {
  109. String generatedCode;
  110. if (GENERATE) {
  111. printText(U"Generating build script\n");
  112. if (language == ScriptLanguage::Batch) {
  113. string_append(generatedCode, U"@echo off\n\n");
  114. } else if (language == ScriptLanguage::Bash) {
  115. string_append(generatedCode, U"#!/bin/bash\n\n");
  116. }
  117. }
  118. // Generate code for compiling source code into objects.
  119. printText(U"Compiling ", input.sourceObjects.length(), U" objects.\n");
  120. for (int64_t o = 0; o < input.sourceObjects.length(); o++) {
  121. SourceObject *sourceObject = &(input.sourceObjects[o]);
  122. printText(U"\t* ", sourceObject->sourcePath, U"\n");
  123. produce_setCompilationFolder<GENERATE>(generatedCode, language, sourceObject->compileFrom);
  124. List<String> compilationArguments;
  125. for (int64_t i = 0; i < sourceObject->compilerFlags.length(); i++) {
  126. compilationArguments.push(sourceObject->compilerFlags[i]);
  127. }
  128. compilationArguments.push(U"-c");
  129. compilationArguments.push(sourceObject->sourcePath);
  130. compilationArguments.push(U"-o");
  131. compilationArguments.push(sourceObject->objectPath);
  132. if (GENERATE) {
  133. if (language == ScriptLanguage::Batch) {
  134. string_append(generatedCode, U"if exist ", sourceObject->objectPath, U" (\n");
  135. } else if (language == ScriptLanguage::Bash) {
  136. string_append(generatedCode, U"if [ -e \"", sourceObject->objectPath, U"\" ]; then\n");
  137. }
  138. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  139. if (language == ScriptLanguage::Batch) {
  140. string_append(generatedCode, U") else (\n");
  141. } else if (language == ScriptLanguage::Bash) {
  142. string_append(generatedCode, U"else\n");
  143. }
  144. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  145. produce_callProgram<GENERATE>(generatedCode, language, sourceObject->compilerName, compilationArguments);
  146. if (language == ScriptLanguage::Batch) {
  147. string_append(generatedCode, U")\n");
  148. } else if (language == ScriptLanguage::Bash) {
  149. string_append(generatedCode, U"fi\n");
  150. }
  151. } else {
  152. if (file_getEntryType(sourceObject->objectPath) == EntryType::File) {
  153. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  154. } else {
  155. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  156. produce_callProgram<GENERATE>(generatedCode, language, sourceObject->compilerName, compilationArguments);
  157. }
  158. }
  159. }
  160. // Generate code for linking objects into executables.
  161. printText(U"Linking ", input.linkerSteps.length(), U" executables:\n");
  162. for (int64_t l = 0; l < input.linkerSteps.length(); l++) {
  163. LinkingStep *linkingStep = &(input.linkerSteps[l]);
  164. String programPath = linkingStep->binaryName;
  165. printText(U"\tLinking ", programPath, U" of :\n");
  166. produce_setCompilationFolder<GENERATE>(generatedCode, language, linkingStep->compileFrom);
  167. List<String> linkerArguments;
  168. // Generate a list of object paths from indices.
  169. String allObjects;
  170. for (int64_t i = 0; i < linkingStep->sourceObjectIndices.length(); i++) {
  171. int64_t objectIndex = linkingStep->sourceObjectIndices[i];
  172. SourceObject *sourceObject = &(input.sourceObjects[objectIndex]);
  173. if (objectIndex >= 0 || objectIndex < input.sourceObjects.length()) {
  174. printText(U"\t\t* ", sourceObject->sourcePath, U"\n");
  175. string_append(allObjects, U" ", sourceObject->objectPath);
  176. linkerArguments.push(sourceObject->objectPath);
  177. } else {
  178. throwError(U"Object index ", objectIndex, U" is out of bound ", 0, U"..", (input.sourceObjects.length() - 1), U"\n");
  179. }
  180. }
  181. String linkerFlags;
  182. for (int64_t l = 0; l < linkingStep->linkerFlags.length(); l++) {
  183. String linkerFlag = linkingStep->linkerFlags[l];
  184. string_append(linkerFlags, U" ", linkerFlag);
  185. linkerArguments.push(linkerFlag);
  186. printText(U"\t\t* ", linkerFlag, U" library\n");
  187. }
  188. linkerArguments.push(U"-o");
  189. linkerArguments.push(programPath);
  190. // Generate the code for building.
  191. if (string_length(linkerFlags) > 0) {
  192. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Linking ", programPath, U" with", linkerFlags, U"."));
  193. } else {
  194. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Linking ", programPath, U"."));
  195. }
  196. produce_callProgram<GENERATE>(generatedCode, language, linkingStep->compilerName, linkerArguments);
  197. if (linkingStep->executeResult) {
  198. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Starting ", programPath));
  199. produce_callProgram<GENERATE>(generatedCode, language, programPath, List<String>());
  200. produce_printMessage<GENERATE>(generatedCode, language, U"The program terminated.");
  201. } else {
  202. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Supressed execution of ", programPath, U" as requested by the project settings."));
  203. }
  204. }
  205. produce_resetCompilationFolder<GENERATE>(generatedCode, language);
  206. produce_printMessage<GENERATE>(generatedCode, language, U"Done building.");
  207. if (GENERATE) {
  208. printText(U"Saving script to ", scriptPath, U":\n", generatedCode, U"\n");
  209. if (language == ScriptLanguage::Batch) {
  210. // Batch on MS-Windows can not recognize a Byte Order Mark, so just encode it as Latin 1.
  211. string_save(scriptPath, generatedCode, CharacterEncoding::Raw_Latin1, LineEncoding::CrLf);
  212. } else if (language == ScriptLanguage::Bash) {
  213. string_save(scriptPath, generatedCode, CharacterEncoding::BOM_UTF8, LineEncoding::Lf);
  214. }
  215. }
  216. }
  217. void generateCompilationScript(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) {
  218. produce<true>(input, scriptPath, language);
  219. }
  220. void executeBuildInstructions(SessionContext &input) {
  221. produce<false>(input, U"", ScriptLanguage::Unknown);
  222. }