#include "generator.h" #include "../../../DFPSR/api/timeAPI.h" using namespace dsr; // Keep track of the current path, so that it only changes when needed. String previousPath; template static void produce_printMessage(String &generatedCode, ScriptLanguage language, const ReadableString message) { if (GENERATE) { if (language == ScriptLanguage::Batch) { string_append(generatedCode, U"echo ", message, U"\n"); } else if (language == ScriptLanguage::Bash) { string_append(generatedCode, U"echo ", message, U"\n"); } } else { printText(message, U"\n"); } } template static void produce_setCompilationFolder(String &generatedCode, ScriptLanguage language, const ReadableString &newPath) { if (GENERATE) { if (!string_match(previousPath, newPath)) { if (string_length(previousPath) > 0) { if (language == ScriptLanguage::Batch) { string_append(generatedCode, U"popd\n"); } else if (language == ScriptLanguage::Bash) { string_append(generatedCode, U")\n"); } } if (string_length(newPath) > 0) { if (language == ScriptLanguage::Batch) { string_append(generatedCode, U"pushd ", newPath, U"\n"); } else if (language == ScriptLanguage::Bash) { string_append(generatedCode, U"(cd ", newPath, U";\n"); } } } previousPath = newPath; } else { if (string_length(newPath) > 0) { if (string_length(previousPath) == 0) { previousPath = file_getCurrentPath(); } file_setCurrentPath(newPath); } } } template static void produce_resetCompilationFolder(String &generatedCode, ScriptLanguage language) { if (GENERATE) { produce_setCompilationFolder(generatedCode, language, U""); } else { if (string_length(previousPath) > 0) { file_setCurrentPath(previousPath); } } } static bool waitForProcess(const DsrProcess &process) { while (true) { DsrProcessStatus status = process_getStatus(process); if (status == DsrProcessStatus::Completed) { return true; } else if (status == DsrProcessStatus::Crashed) { return false; } else if (status == DsrProcessStatus::NotStarted) { return false; } time_sleepSeconds(0.001); } } template static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath, const List &arguments) { if (GENERATE) { string_append(generatedCode, programPath); for (int64_t a = 0; a < arguments.length(); a++) { // 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. string_append(generatedCode, U" ", arguments[a]); } string_append(generatedCode, U"\n"); } else { // Print each external call in the terminal, because there is no script to inspect when not generating. if (arguments.length() > 0) { printText(U"Calling ", programPath, U" with"); for (int64_t a = 0; a < arguments.length(); a++) { printText(U" ", arguments[a]); } printText(U"\n"); } else { printText(U"Calling ", programPath, U"\n"); } // TODO: How can multiple calls be made to the compiler at the same time and only wait for all before linking? // Don't want to break control flow from the code generating a serial script, so maybe a waitForAll command before performing any linking. // Don't want error messages from multiple failed compilations to collide in the same terminal. if (file_getEntryType(programPath) != EntryType::File) { throwError(U"Failed to execute ", programPath, U", because the executable file was not found!\n"); } else { if (!waitForProcess(process_execute(programPath, arguments))) { throwError(U"Failed to execute ", programPath, U"!\n"); } } } } template static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath) { produce_callProgram(generatedCode, language, programPath, List()); } template void produce(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) { String generatedCode; if (GENERATE) { printText(U"Generating build script\n"); if (language == ScriptLanguage::Batch) { string_append(generatedCode, U"@echo off\n\n"); } else if (language == ScriptLanguage::Bash) { string_append(generatedCode, U"#!/bin/bash\n\n"); } } // Generate code for compiling source code into objects. printText(U"Compiling ", input.sourceObjects.length(), U" objects.\n"); for (int64_t o = 0; o < input.sourceObjects.length(); o++) { SourceObject *sourceObject = &(input.sourceObjects[o]); printText(U"\t* ", sourceObject->sourcePath, U"\n"); produce_setCompilationFolder(generatedCode, language, sourceObject->compileFrom); List compilationArguments; for (int64_t i = 0; i < sourceObject->compilerFlags.length(); i++) { compilationArguments.push(sourceObject->compilerFlags[i]); } compilationArguments.push(U"-c"); compilationArguments.push(sourceObject->sourcePath); compilationArguments.push(U"-o"); compilationArguments.push(sourceObject->objectPath); if (GENERATE) { if (language == ScriptLanguage::Batch) { string_append(generatedCode, U"if exist ", sourceObject->objectPath, U" (\n"); } else if (language == ScriptLanguage::Bash) { string_append(generatedCode, U"if [ -e \"", sourceObject->objectPath, U"\" ]; then\n"); } produce_printMessage(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U".")); if (language == ScriptLanguage::Batch) { string_append(generatedCode, U") else (\n"); } else if (language == ScriptLanguage::Bash) { string_append(generatedCode, U"else\n"); } produce_printMessage(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U".")); produce_callProgram(generatedCode, language, sourceObject->compilerName, compilationArguments); if (language == ScriptLanguage::Batch) { string_append(generatedCode, U")\n"); } else if (language == ScriptLanguage::Bash) { string_append(generatedCode, U"fi\n"); } } else { if (file_getEntryType(sourceObject->objectPath) == EntryType::File) { produce_printMessage(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U".")); } else { produce_printMessage(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U".")); produce_callProgram(generatedCode, language, sourceObject->compilerName, compilationArguments); } } } // Generate code for linking objects into executables. printText(U"Linking ", input.linkerSteps.length(), U" executables:\n"); for (int64_t l = 0; l < input.linkerSteps.length(); l++) { LinkingStep *linkingStep = &(input.linkerSteps[l]); String programPath = linkingStep->binaryName; printText(U"\tLinking ", programPath, U" of :\n"); produce_setCompilationFolder(generatedCode, language, linkingStep->compileFrom); List linkerArguments; // Generate a list of object paths from indices. String allObjects; for (int64_t i = 0; i < linkingStep->sourceObjectIndices.length(); i++) { int64_t objectIndex = linkingStep->sourceObjectIndices[i]; SourceObject *sourceObject = &(input.sourceObjects[objectIndex]); if (objectIndex >= 0 || objectIndex < input.sourceObjects.length()) { printText(U"\t\t* ", sourceObject->sourcePath, U"\n"); string_append(allObjects, U" ", sourceObject->objectPath); linkerArguments.push(sourceObject->objectPath); } else { throwError(U"Object index ", objectIndex, U" is out of bound ", 0, U"..", (input.sourceObjects.length() - 1), U"\n"); } } String linkerFlags; for (int64_t l = 0; l < linkingStep->linkerFlags.length(); l++) { String linkerFlag = linkingStep->linkerFlags[l]; string_append(linkerFlags, U" ", linkerFlag); linkerArguments.push(linkerFlag); printText(U"\t\t* ", linkerFlag, U" library\n"); } linkerArguments.push(U"-o"); linkerArguments.push(programPath); // Generate the code for building. if (string_length(linkerFlags) > 0) { produce_printMessage(generatedCode, language, string_combine(U"Linking ", programPath, U" with", linkerFlags, U".")); } else { produce_printMessage(generatedCode, language, string_combine(U"Linking ", programPath, U".")); } produce_callProgram(generatedCode, language, linkingStep->compilerName, linkerArguments); if (linkingStep->executeResult) { produce_printMessage(generatedCode, language, string_combine(U"Starting ", programPath)); produce_callProgram(generatedCode, language, programPath, List()); produce_printMessage(generatedCode, language, U"The program terminated."); } else { produce_printMessage(generatedCode, language, string_combine(U"Supressed execution of ", programPath, U" as requested by the project settings.")); } } produce_resetCompilationFolder(generatedCode, language); produce_printMessage(generatedCode, language, U"Done building."); if (GENERATE) { printText(U"Saving script to ", scriptPath, U":\n", generatedCode, U"\n"); if (language == ScriptLanguage::Batch) { // Batch on MS-Windows can not recognize a Byte Order Mark, so just encode it as Latin 1. string_save(scriptPath, generatedCode, CharacterEncoding::Raw_Latin1, LineEncoding::CrLf); } else if (language == ScriptLanguage::Bash) { string_save(scriptPath, generatedCode, CharacterEncoding::BOM_UTF8, LineEncoding::Lf); } } } void generateCompilationScript(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) { produce(input, scriptPath, language); } void executeBuildInstructions(SessionContext &input) { produce(input, U"", ScriptLanguage::Unknown); }