Преглед на файлове

Letting the build system call the compiler directly.

David Piuva преди 3 години
родител
ревизия
ae6c123b9b

+ 35 - 13
Source/tools/builder/buildProject.bat

@@ -1,3 +1,20 @@
+
+rem Local build settings that should be configured before building for the first time.
+
+set CPP_COMPILER_FOLDER=C:\Program\CodeBlocks\MinGW\bin
+set CPP_COMPILER_PATH=%CPP_COMPILER_FOLDER%\x86_64-w64-mingw32-g++.exe
+
+rem Change the temporary folder if want generated scripts and objects to go somewhere else.
+set TEMPORARY_FOLDER=%TEMP%
+
+rem Select build method. (Not generating a script requires having the full path of the compiler.)
+rem set GENERATE_SCRIPT=Yes
+set GENERATE_SCRIPT=No
+
+
+
+
+
 @echo off
 
 rem Using buildProject.bat
@@ -14,11 +31,9 @@ set BUILDER_EXECUTABLE=%BUILDER_FOLDER%builder.exe
 echo BUILDER_EXECUTABLE = %BUILDER_EXECUTABLE%
 set DFPSR_LIBRARY=%BUILDER_FOLDER%..\..\DFPSR
 echo DFPSR_LIBRARY = %DFPSR_LIBRARY%
-set BUILDER_SOURCE=%BUILDER_FOLDER%\code\main.cpp %BUILDER_FOLDER%\code\Machine.cpp %BUILDER_FOLDER%\code\generator.cpp %BUILDER_FOLDER%\code\analyzer.cpp %BUILDER_FOLDER%\code\expression.cpp %DFPSR_LIBRARY%\collection\collections.cpp %DFPSR_LIBRARY%\api\fileAPI.cpp %DFPSR_LIBRARY%\api\bufferAPI.cpp %DFPSR_LIBRARY%\api\stringAPI.cpp %DFPSR_LIBRARY%\base\SafePointer.cpp
+set BUILDER_SOURCE=%BUILDER_FOLDER%\code\main.cpp %BUILDER_FOLDER%\code\Machine.cpp %BUILDER_FOLDER%\code\generator.cpp %BUILDER_FOLDER%\code\analyzer.cpp %BUILDER_FOLDER%\code\expression.cpp %DFPSR_LIBRARY%\collection\collections.cpp %DFPSR_LIBRARY%\api\fileAPI.cpp %DFPSR_LIBRARY%\api\bufferAPI.cpp %DFPSR_LIBRARY%\api\stringAPI.cpp %DFPSR_LIBRARY%\api\timeAPI.cpp %DFPSR_LIBRARY%\base\SafePointer.cpp
 echo BUILDER_SOURCE = %BUILDER_SOURCE%
 
-set CPP_COMPILER_FOLDER=C:\Program\CodeBlocks\MinGW\bin
-set CPP_COMPILER_PATH=%CPP_COMPILER_FOLDER%\x86_64-w64-mingw32-g++.exe
 echo Change CPP_COMPILER_FOLDER and CPP_COMPILER_PATH in %BUILDER_FOLDER%\buildProject.bat if you are not using %CPP_COMPILER_PATH% as your compiler.
 
 rem Check if the build system is compiled
@@ -38,15 +53,22 @@ if exist %BUILDER_EXECUTABLE% (
 	)
 )
 
-rem Call the build system with a filename for the output script, which is later called.
-set SCRIPT_PATH=%TEMP%\dfpsr_compile.bat
-echo Generating %SCRIPT_PATH% from %1%
-if exist %SCRIPT_PATH% (
-	del %SCRIPT_PATH%
-)
-%BUILDER_EXECUTABLE% %SCRIPT_PATH% %* Compiler=%CPP_COMPILER_PATH% CompileFrom=%CPP_COMPILER_FOLDER%
-if exist %SCRIPT_PATH% (
-	echo Running %SCRIPT_PATH%
-	%SCRIPT_PATH%
+if !GENERATE_SCRIPT! EQU Yes (
+	rem Call the build system with a filename for the output script, which is later called.
+	set SCRIPT_PATH=%TEMPORARY_FOLDER%\dfpsr_compile.bat
+	echo Generating %SCRIPT_PATH% from %1%
+	if exist %SCRIPT_PATH% (
+		del %SCRIPT_PATH%
+	)
+	%BUILDER_EXECUTABLE% %SCRIPT_PATH% %* Compiler=%CPP_COMPILER_PATH% CompileFrom=%CPP_COMPILER_FOLDER%
+	if exist %SCRIPT_PATH% (
+		echo Running %SCRIPT_PATH%
+		%SCRIPT_PATH%
+	)
+) else (
+	rem Calling the build system with only the temporary folder will call the compiler directly from the build system.
+	rem   A simpler solution that works with just a single line, once the build system itself has been compiled.
+	%BUILDER_EXECUTABLE% %TEMPORARY_FOLDER% %* Compiler=%CPP_COMPILER_PATH% CompileFrom=%CPP_COMPILER_FOLDER%
 )
+
 pause

+ 39 - 16
Source/tools/builder/buildProject.sh

@@ -1,3 +1,20 @@
+
+# Local build settings that should be configured before building for the first time.
+
+CPP_COMPILER_PATH="/usr/bin/g++"
+echo "Change CPP_COMPILER_PATH in ${BUILDER_FOLDER}/buildProject.sh if you are not using ${CPP_COMPILER_PATH} as your compiler."
+
+# Change TEMPORARY_FOLDER if you don't want to recompile everything after each reboot, or your operating system has a different path to the temporary folder.
+TEMPORARY_FOLDER="/tmp"
+
+# Select build method. (Not generating a script requires having the full path of the compiler.)
+#GENERATE_SCRIPT="Yes"
+GENERATE_SCRIPT="No"
+
+
+
+
+
 # Using buildProject.sh
 #   $1 must be the *.DsrProj path, which is relative to the caller location.
 #   $2... are variable assignments sent as input to the given project file.
@@ -11,16 +28,13 @@ echo "BUILDER_FOLDER = ${BUILDER_FOLDER}"
 BUILDER_EXECUTABLE="${BUILDER_FOLDER}/builder"
 echo "BUILDER_EXECUTABLE = ${BUILDER_EXECUTABLE}"
 
-CPP_COMPILER_PATH="g++"
-echo "Change CPP_COMPILER_PATH in ${BUILDER_FOLDER}/buildProject.sh if you are not using ${CPP_COMPILER_PATH} as your compiler."
-
 # Check if the build system is compiled
 if [ -e "${BUILDER_EXECUTABLE}" ]; then
 	echo "Found the build system's binary."
 else
 	echo "Building the Builder build system for first time use."
 	LIBRARY_PATH="$(realpath ${BUILDER_FOLDER}/../../DFPSR)"
-	SOURCE_CODE="${BUILDER_FOLDER}/code/main.cpp ${BUILDER_FOLDER}/code/Machine.cpp ${BUILDER_FOLDER}/code/generator.cpp ${BUILDER_FOLDER}/code/analyzer.cpp ${BUILDER_FOLDER}/code/expression.cpp ${LIBRARY_PATH}/collection/collections.cpp ${LIBRARY_PATH}/api/fileAPI.cpp ${LIBRARY_PATH}/api/bufferAPI.cpp ${LIBRARY_PATH}/api/stringAPI.cpp ${LIBRARY_PATH}/base/SafePointer.cpp"
+	SOURCE_CODE="${BUILDER_FOLDER}/code/main.cpp ${BUILDER_FOLDER}/code/Machine.cpp ${BUILDER_FOLDER}/code/generator.cpp ${BUILDER_FOLDER}/code/analyzer.cpp ${BUILDER_FOLDER}/code/expression.cpp ${LIBRARY_PATH}/collection/collections.cpp ${LIBRARY_PATH}/api/fileAPI.cpp ${LIBRARY_PATH}/api/bufferAPI.cpp ${LIBRARY_PATH}/api/stringAPI.cpp ${LIBRARY_PATH}/api/timeAPI.cpp ${LIBRARY_PATH}/base/SafePointer.cpp"
 	"${CPP_COMPILER_PATH}" -o "${BUILDER_EXECUTABLE}" ${SOURCE_CODE} -std=c++14
 	if [ $? -eq 0 ]; then
 		echo "Completed building the Builder build system."
@@ -31,16 +45,25 @@ else
 fi
 chmod +x "${BUILDER_EXECUTABLE}"
 
-# Call the build system with a filename for the output script, which is later given execution permission and called.
-SCRIPT_PATH="/tmp/dfpsr_compile.sh"
-echo "Generating ${SCRIPT_PATH} from $1"
-if [ -e "${SCRIPT_PATH}" ]; then
-	rm "${SCRIPT_PATH}"
-fi
-"${BUILDER_EXECUTABLE}" "${SCRIPT_PATH}" "$@" "Compiler=${CPP_COMPILER_PATH}";
-if [ -e "${SCRIPT_PATH}" ]; then
-	echo "Giving execution permission to ${SCRIPT_PATH}"
-	chmod +x "${SCRIPT_PATH}"
-	echo "Running ${SCRIPT_PATH}"
-	"${SCRIPT_PATH}"
+if [ "$GENERATE_SCRIPT" == "Yes" ]; then
+	# Calling the build system with a script path will generate it with compiling and linking commands before executing the result.
+	#   Useful for debugging the output when something goes wrong.
+	SCRIPT_PATH="${TEMPORARY_FOLDER}/dfpsr_compile.sh"
+	echo "Generating ${SCRIPT_PATH} from $1"
+	if [ -e "${SCRIPT_PATH}" ]; then
+		rm "${SCRIPT_PATH}"
+	fi
+	"${BUILDER_EXECUTABLE}" "${SCRIPT_PATH}" "$@" "Compiler=${CPP_COMPILER_PATH}";
+	if [ -e "${SCRIPT_PATH}" ]; then
+		echo "Giving execution permission to ${SCRIPT_PATH}"
+		chmod +x "${SCRIPT_PATH}"
+		echo "Running ${SCRIPT_PATH}"
+		"${SCRIPT_PATH}"
+	fi
+else
+	# Calling the build system with only the temporary folder will call the compiler directly from the build system.
+	#   A simpler solution that works with just a single line, once the build system itself has been compiled.
+	echo "Generating objects to ${TEMPORARY_FOLDER} from $1"
+	"${BUILDER_EXECUTABLE}" "${TEMPORARY_FOLDER}" "$@" "Compiler=${CPP_COMPILER_PATH}";
 fi
+

+ 1 - 1
Source/tools/builder/code/analyzer.cpp

@@ -346,7 +346,7 @@ void gatherBuildInstructions(SessionContext &output, ProjectContext &context, Ma
 				uint64_t combinedChecksum = getCombinedChecksum(context, d);
 				String objectPath = file_combinePaths(output.tempPath, string_combine(U"dfpsr_", identityChecksum, U"_", combinedChecksum, U".o"));
 				sourceObjectIndices.push(output.sourceObjects.length());
-				output.sourceObjects.pushConstruct(identityChecksum, combinedChecksum, sourcePath, objectPath, generatedCompilerFlags, compilerName, compileFrom);
+				output.sourceObjects.pushConstruct(identityChecksum, combinedChecksum, sourcePath, objectPath, settings.compilerFlags, compilerName, compileFrom);
 			} else {
 				// Link to this pre-existing source file.
 				sourceObjectIndices.push(previousIndex);

+ 9 - 4
Source/tools/builder/code/builderTypes.h

@@ -30,7 +30,11 @@ struct Machine {
 };
 
 enum class Extension {
-	Unknown, H, Hpp, C, Cpp
+	Unknown,
+	H,
+	Hpp,
+	C,
+	Cpp
 };
 
 enum class ScriptLanguage {
@@ -68,9 +72,10 @@ struct ProjectContext {
 struct SourceObject {
 	uint64_t identityChecksum = 0; // Identification number for the object's name.
 	uint64_t combinedChecksum = 0; // Combined content of the source file and all included headers recursively.
-	String sourcePath, objectPath, generatedCompilerFlags, compilerName, compileFrom;
-	SourceObject(uint64_t identityChecksum, uint64_t combinedChecksum, const ReadableString& sourcePath, const ReadableString& objectPath, const ReadableString& generatedCompilerFlags, const ReadableString& compilerName, const ReadableString& compileFrom)
-	: identityChecksum(identityChecksum), combinedChecksum(combinedChecksum), sourcePath(sourcePath), objectPath(objectPath), generatedCompilerFlags(generatedCompilerFlags), compilerName(compilerName), compileFrom(compileFrom) {}
+	String sourcePath, objectPath, compilerName, compileFrom;
+	List<String> compilerFlags;
+	SourceObject(uint64_t identityChecksum, uint64_t combinedChecksum, const ReadableString& sourcePath, const ReadableString& objectPath, const List<String> &compilerFlags, const ReadableString& compilerName, const ReadableString& compileFrom)
+	: identityChecksum(identityChecksum), combinedChecksum(combinedChecksum), sourcePath(sourcePath), objectPath(objectPath), compilerFlags(compilerFlags), compilerName(compilerName), compileFrom(compileFrom) {}
 };
 
 struct LinkingStep {

+ 173 - 79
Source/tools/builder/code/generator.cpp

@@ -1,101 +1,176 @@
 
 #include "generator.h"
+#include "../../../DFPSR/api/timeAPI.h"
 
 using namespace dsr;
 
-static ScriptLanguage identifyLanguage(const ReadableString &filename) {
-	String scriptExtension = string_upperCase(file_getExtension(filename));
-	if (string_match(scriptExtension, U"BAT")) {
-		return ScriptLanguage::Batch;
-	} else if (string_match(scriptExtension, U"SH")) {
-		return ScriptLanguage::Bash;
+// Keep track of the current path, so that it only changes when needed.
+String previousPath;
+
+template <bool GENERATE>
+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 {
-		throwError(U"Could not identify the scripting language of ", filename, U". Use *.bat or *.sh.\n");
-		return ScriptLanguage::Unknown;
+		printText(message, U"\n");
 	}
 }
 
-static void script_printMessage(String &output, ScriptLanguage language, const ReadableString message) {
-	if (language == ScriptLanguage::Batch) {
-		string_append(output, U"echo ", message, U"\n");
-	} else if (language == ScriptLanguage::Bash) {
-		string_append(output, U"echo ", message, U"\n");
+template <bool GENERATE>
+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,  "popd\n");
+				} else if (language == ScriptLanguage::Bash) {
+					string_append(generatedCode, U")\n");
+				}
+			}
+			if (string_length(newPath) > 0) {
+				if (language == ScriptLanguage::Batch) {
+					string_append(generatedCode,  "pushd ", newPath, "\n");
+				} else if (language == ScriptLanguage::Bash) {
+					string_append(generatedCode, U"(cd ", newPath, ";\n");
+				}
+			}
+		}
+		previousPath = newPath;
+	} else {
+		if (string_length(newPath) > 0) {
+			if (string_length(previousPath) == 0) {
+				previousPath = file_getCurrentPath();
+			}
+			file_setCurrentPath(newPath);
+		}
 	}
 }
 
-static void setCompilationFolder(String &generatedCode, ScriptLanguage language, String &currentPath, const ReadableString &newPath) {
-	if (!string_match(currentPath, newPath)) {
-		if (string_length(currentPath) > 0) {
-			if (language == ScriptLanguage::Batch) {
-				string_append(generatedCode,  "popd\n");
-			} else if (language == ScriptLanguage::Bash) {
-				string_append(generatedCode, U")\n");
-			}
+template <bool GENERATE>
+static void produce_resetCompilationFolder(String &generatedCode, ScriptLanguage language) {
+	if (GENERATE) {
+		produce_setCompilationFolder<true>(generatedCode, language, U"");
+	} else {
+		if (string_length(previousPath) > 0) {
+			file_setCurrentPath(previousPath);
 		}
-		if (string_length(newPath) > 0) {
-			if (language == ScriptLanguage::Batch) {
-				string_append(generatedCode,  "pushd ", newPath, "\n");
-			} else if (language == ScriptLanguage::Bash) {
-				string_append(generatedCode, U"(cd ", newPath, ";\n");
+	}
+}
+
+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;
+		}
+		time_sleepSeconds(0.001);
+	}
+}
+
+template <bool GENERATE>
+static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath, const List<String> &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 (!waitForProcess(process_execute(programPath, arguments))) {
+			printText(U"Failed to execute ", programPath, U"!\n");
+			exit(0);
 		}
 	}
-	currentPath = newPath;
 }
 
-void generateCompilationScript(SessionContext &input, const ReadableString &scriptPath) {
-	printText(U"Generating build script\n");
+template <bool GENERATE>
+static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath) {
+	produce_callProgram<GENERATE>(generatedCode, language, programPath, List<String>());
+}
+
+template <bool GENERATE>
+void produce(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) {
 	String generatedCode;
-	ScriptLanguage language = identifyLanguage(scriptPath);
-	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");
+	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");
+		}
 	}
 
-	// Keep track of the current path, so that it only changes when needed.
-	String currentPath;
-
 	// Generate code for compiling source code into objects.
-	printText(U"Generating code for compiling ", input.sourceObjects.length(), U" objects.\n");
+	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");
-		setCompilationFolder(generatedCode, language, currentPath, sourceObject->compileFrom);
-		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");
-		}
-		script_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_setCompilationFolder<GENERATE>(generatedCode, language, sourceObject->compileFrom);
+		List<String> compilationArguments;
+		for (int64_t i = 0; i < sourceObject->compilerFlags.length(); i++) {
+			compilationArguments.push(sourceObject->compilerFlags[i]);
 		}
-		String compilerFlags = sourceObject->generatedCompilerFlags;
-		script_printMessage(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U" with ", compilerFlags, U"."));
-		string_append(generatedCode, sourceObject->compilerName, compilerFlags, U" -c ", sourceObject->sourcePath, U" -o ", sourceObject->objectPath, U"\n");
-		if (language == ScriptLanguage::Batch) {
-			string_append(generatedCode,  ")\n");
-		} else if (language == ScriptLanguage::Bash) {
-			string_append(generatedCode, U"fi\n");
+		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<GENERATE>(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<GENERATE>(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
+			produce_callProgram<GENERATE>(generatedCode, language, sourceObject->compilerName, compilationArguments);
+			if (language == ScriptLanguage::Batch) {
+				string_append(generatedCode,  ")\n");
+			} else if (language == ScriptLanguage::Bash) {
+				string_append(generatedCode, U"fi\n");
+			}
+		} else {
+			if (file_getEntryType(sourceObject->objectPath) == EntryType::File) {
+				produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
+			} else {
+				produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
+				produce_callProgram<GENERATE>(generatedCode, language, sourceObject->compilerName, compilationArguments);
+			}
 		}
 	}
 
 	// Generate code for linking objects into executables.
-	printText(U"Generating code for linking ", input.linkerSteps.length(), U" executables:\n");
+	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"\tGenerating code for linking ", programPath, U" of :\n");
-		setCompilationFolder(generatedCode, language, currentPath, linkingStep->compileFrom);
-		String linkerFlags;
-		for (int64_t lib = 0; lib < linkingStep->linkerFlags.length(); lib++) {
-			String linkerFlag = linkingStep->linkerFlags[lib];
-			string_append(linkerFlags, " ", linkerFlag);
-			printText(U"\t\t* ", linkerFlag, U" library\n");
-		}
+		printText(U"\tLinking ", programPath, U" of :\n");
+		produce_setCompilationFolder<GENERATE>(generatedCode, language, linkingStep->compileFrom);
+		List<String> linkerArguments;
 		// Generate a list of object paths from indices.
 		String allObjects;
 		for (int64_t i = 0; i < linkingStep->sourceObjectIndices.length(); i++) {
@@ -104,31 +179,50 @@ void generateCompilationScript(SessionContext &input, const ReadableString &scri
 			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, " ", 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) {
-			script_printMessage(generatedCode, language, string_combine(U"Linking ", programPath, U" with", linkerFlags, U"."));
+			produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Linking ", programPath, U" with", linkerFlags, U"."));
 		} else {
-			script_printMessage(generatedCode, language, string_combine(U"Linking ", programPath, U"."));
+			produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Linking ", programPath, U"."));
 		}
-		string_append(generatedCode, linkingStep->compilerName, allObjects, linkerFlags, U" -o ", programPath, U"\n");
+		produce_callProgram<GENERATE>(generatedCode, language, linkingStep->compilerName, linkerArguments);
 		if (linkingStep->executeResult) {
-			script_printMessage(generatedCode, language, string_combine(U"Starting ", programPath));
-			string_append(generatedCode, programPath, U"\n");
-			script_printMessage(generatedCode, language, U"The program terminated.");
+			produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Starting ", programPath));
+			produce_callProgram<GENERATE>(generatedCode, language, programPath, List<String>());
+			produce_printMessage<GENERATE>(generatedCode, language, U"The program terminated.");
 		}
 	}
-	setCompilationFolder(generatedCode, language, currentPath, U"");
-	script_printMessage(generatedCode, language, U"Done building.");
+	produce_resetCompilationFolder<GENERATE>(generatedCode, language);
+	produce_printMessage<GENERATE>(generatedCode, language, U"Done building.");
 
-	// Save the script.
-	printText(U"Saving script to ", scriptPath, "\n");
-	if (language == ScriptLanguage::Batch) {
-		string_save(scriptPath, generatedCode);
-	} else if (language == ScriptLanguage::Bash) {
-		string_save(scriptPath, generatedCode, CharacterEncoding::BOM_UTF8, LineEncoding::Lf);
+	if (GENERATE) {
+		printText(U"Saving script to ", scriptPath, "\n");
+		if (language == ScriptLanguage::Batch) {
+			string_save(scriptPath, generatedCode);
+		} else if (language == ScriptLanguage::Bash) {
+			string_save(scriptPath, generatedCode, CharacterEncoding::BOM_UTF8, LineEncoding::Lf);
+		}
 	}
 }
+
+void generateCompilationScript(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) {
+	produce<true>(input, scriptPath, language);
+}
+
+void executeBuildInstructions(SessionContext &input) {
+	produce<false>(input, U"", ScriptLanguage::Unknown);
+}

+ 5 - 1
Source/tools/builder/code/generator.h

@@ -7,6 +7,10 @@
 
 using namespace dsr;
 
-void generateCompilationScript(SessionContext &output, const ReadableString &scriptPath);
+// Generating a script to execute later.
+void generateCompilationScript(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language);
+
+// Calling the compiler directly.
+void executeBuildInstructions(SessionContext &input);
 
 #endif

+ 72 - 10
Source/tools/builder/code/main.cpp

@@ -8,8 +8,9 @@
 //      Import <DFPSR>
 //    instead of
 //      Import "../../DFPSR/DFPSR.DsrHead"
-//  * Call the compiler directly when the temp folder is given without any script name.
-//    Use it to run multiple instances of the compiler at the same time on different CPU cores.
+//  * Give a warning when the given compiler path is not actually a path to a file and script generation is disabled.
+//    Also make the compiler's path absolute from the current directory when called, or the specified folder to call from.
+//  * Run multiple instances of the compiler at the same time on different CPU cores.
 //  * Improve entropy in checksums using a more advanced algorithm to reduce the risk of conflicts.
 //  * Implement more features for the machine, such as:
 //    * else and elseif cases.
@@ -79,7 +80,28 @@ Project files:
 
 using namespace dsr;
 
-// List dependencies for main.cpp on Linux: ./builder main.cpp --depend
+ScriptLanguage identifyLanguage(const ReadableString &filename) {
+	String scriptExtension = string_upperCase(file_getExtension(filename));
+	if (string_match(scriptExtension, U"BAT")) {
+		return ScriptLanguage::Batch;
+	} else if (string_match(scriptExtension, U"SH")) {
+		return ScriptLanguage::Bash;
+	} else {
+		return ScriptLanguage::Unknown;
+	}
+}
+
+// Approximate syntax:
+//   outputPath <- tempFolder | tempFolder/scriptName.sh | tempFolder\scriptName.bat
+//   key <- SkipIfBinaryExists | Supressed | ProgramPath | Compiler | CompileFrom | Debug | StaticRuntime | Optimization | (a..z|A..Z)(0..9|a..z|A..Z)*
+//   flag <- key | key=value
+//   buildCall <- builderPath outputPath projectPath flag*
+// Example uses:
+//   Build Wizard.DsrProj for Linux using the g++ compiler by generating dfpsr_compile.sh and *.o objects in the /tmp folder.
+//     ../builder/builder /tmp/dfpsr_compile.sh ./Wizard.DsrProj Compiler=g++ Linux
+//   One can also just give the temporary folder to have the compiler called directly.
+//     ../builder/builder /tmp ./Wizard.DsrProj Compiler=g++ Linux
+
 DSR_MAIN_CALLER(dsrMain)
 void dsrMain(List<String> args) {
 	if (args.length() <= 1) {
@@ -89,11 +111,47 @@ void dsrMain(List<String> args) {
 		printText(U"To use the DFPSR build system, pass a path to a script to generate, a project file or folder containing multiple projects, and the flags you want assigned before building.\n");
 		printText(U"To run regression tests, don't pass any argument to the program.\n");
 	} else {
-		// Get the script's destination path for all projects built during the session as the first argument.
-		String scriptPath = args[1];
-		String tempFolder = file_getAbsoluteParentFolder(scriptPath);
-		// Get the first project file's path, or a folder path containing all projects to build.
+		// Print the full command to show the caller if the arguments got messed up.
+		printText(U"Build command:");
+		for (int i = 0; i < args.length(); i++) {
+			printText(U" ", args[i]);
+		}
+		printText(U"\n");
+		// Get the script's destination path, or the temporary folder for all projects built during the session as the first argument.
+		String outputPath = args[1];
+		String scriptPath, tempFolder;
+		ScriptLanguage language = ScriptLanguage::Unknown;
+		if (file_getEntryType(outputPath) == EntryType::Folder) {
+			printText(U"The output path is a folder.\n");
+			// Not creating a script is useful if the operating system does not support any of the generated script languages.
+			tempFolder = outputPath;
+		} else {
+			// Creating a script is useful for understanding what went wrong when building fails.
+			language = identifyLanguage(outputPath);
+			if (language == ScriptLanguage::Unknown) {
+				printText(U"Could not identify the scripting language of \"", outputPath, U"\". Use *.bat, *.sh or just a temporary folder path to call the compiler directly.\n");
+				return;
+			}
+			printText(U"The output path is a script file.\n");
+			scriptPath = outputPath;
+			tempFolder = file_getAbsoluteParentFolder(outputPath);
+		}
+		printText(U"Using ", tempFolder, U" as the temporary folder for compiled objects.\n");
+		if (string_length(scriptPath) > 0) {
+			printText(U"Using ", scriptPath, U" as the temporary script for calling the compiler.\n");
+		} else {
+			printText(U"No script path was given. The compiler will be called directly instead.\n");
+		}
+		// Get the project file's path, or a folder path containing all projects to build.
 		String projectPath = args[2];
+		String projectExtension = string_upperCase(file_getExtension(projectPath));
+		if (string_match(projectExtension, U"DSRHEAD")) {
+			printText(U"The path ", projectPath, U" does not refer to a project file. *.DsrHead is imported into projects to automate build configurations for users of a specific library.\n");
+			return;
+		} else if (!string_match(projectExtension, U"DSRPROJ")) {
+			printText(U"The path ", projectPath, U" does not refer to a project file, because it does not have the *.DsrProj extension.\n");
+			return;
+		}
 		// Read the reas after the project's path, as named integers assigned to ones.
 		// Calling builder with the extra arguments will interpret them as variables and mark them as inherited, so that they are passed on to any other projects build from the project file.
 		// Other values can be assigned using an equality sign.
@@ -109,8 +167,12 @@ void dsrMain(List<String> args) {
 		SessionContext buildContext = SessionContext(tempFolder, executableExtension);
 		build(buildContext, projectPath, settings);
 		validateSettings(settings, U"in settings after executing the root build script (in main)");
-		// Generate a script to execute.
-		// TODO: Store compiler flags in groups of lists to allow taking them directly as program arguments when calling the compiler directly.
-		generateCompilationScript(buildContext, scriptPath);
+		if (language == ScriptLanguage::Unknown) {
+			// Call the compiler directly.
+			executeBuildInstructions(buildContext);
+		} else {
+			// Generate a script to execute.
+			generateCompilationScript(buildContext, scriptPath, language);
+		}
 	}
 }