소스 검색

Building multiple projects using a single command.

David Piuva 3 년 전
부모
커밋
665e2ac962

+ 136 - 18
Source/tools/builder/Machine.cpp

@@ -5,6 +5,21 @@
 
 using namespace dsr;
 
+Extension extensionFromString(const ReadableString& extensionName) {
+	String upperName = string_upperCase(string_removeOuterWhiteSpace(extensionName));
+	Extension result = Extension::Unknown;
+	if (string_match(upperName, U"H")) {
+		result = Extension::H;
+	} else if (string_match(upperName, U"HPP")) {
+		result = Extension::Hpp;
+	} else if (string_match(upperName, U"C")) {
+		result = Extension::C;
+	} else if (string_match(upperName, U"CPP")) {
+		result = Extension::Cpp;
+	}
+	return result;
+}
+
 int64_t findFlag(const Machine &target, const dsr::ReadableString &key) {
 	for (int64_t f = 0; f < target.variables.length(); f++) {
 		if (string_caseInsensitiveMatch(key, target.variables[f].key)) {
@@ -40,12 +55,15 @@ static String unwrapIfNeeded(const dsr::ReadableString &value) {
 	}
 }
 
-void assignValue(Machine &target, const dsr::ReadableString &key, const dsr::ReadableString &value) {
+void assignValue(Machine &target, const dsr::ReadableString &key, const dsr::ReadableString &value, bool inherited) {
 	int64_t existingIndex = findFlag(target, key);
 	if (existingIndex == -1) {
-		target.variables.pushConstruct(string_upperCase(key), unwrapIfNeeded(value));
+		target.variables.pushConstruct(string_upperCase(key), unwrapIfNeeded(value), inherited);
 	} else {
 		target.variables[existingIndex].value = unwrapIfNeeded(value);
+		if (inherited) {
+			target.variables[existingIndex].inherited = true;
+		}
 	}
 }
 
@@ -145,23 +163,30 @@ static String evaluateExpression(Machine &target, List<String> &tokens, int64_t
 	return U"?";
 }
 
-static void analyzeSource(const dsr::ReadableString &absolutePath) {
+static void crawlSource(ProjectContext &context, const dsr::ReadableString &absolutePath) {
 	EntryType pathType = file_getEntryType(absolutePath);
 	if (pathType == EntryType::File) {
-		printText(U"Using source from ", absolutePath, U".\n");
-		analyzeFromFile(absolutePath);
+		printText(U"Crawling for source from ", absolutePath, U".\n");
+		analyzeFromFile(context, absolutePath);
 	} else if (pathType == EntryType::Folder) {
-		// TODO: Being analyzing from each source file in the folder recursively.
-		//       Each file that is already included will quickly be ignored.
-		//       The difficult part is that exploring a folder returns files in non-deterministic order and GNU's compiler is order dependent.
-		printText(U"Searching for source code from the folder ", absolutePath, U" is not yet supported due to order dependent linking!\n");
+		printText(U"Crawling was given the folder ", absolutePath, U" but a source file was expected!\n");
 	} else if (pathType == EntryType::SymbolicLink) {
 		// Symbolic links can point to both files and folder, so we need to follow it and find out what it really is.
-		analyzeSource(file_followSymbolicLink(absolutePath));
+		crawlSource(context, file_followSymbolicLink(absolutePath));
+	}
+}
+
+// Copy inherited variables from parent to child.
+static void inheritMachine(Machine &child, const Machine &parent) {
+	for (int v = 0; v < parent.variables.length(); v++) {
+		String key = string_upperCase(parent.variables[v].key);
+		if (parent.variables[v].inherited) {
+			child.variables.push(parent.variables[v]);
+		}
 	}
 }
 
-static void interpretLine(Machine &target, List<String> &tokens, const dsr::ReadableString &fromPath) {
+static void interpretLine(ScriptTarget &output, ProjectContext &context, Machine &target, List<String> &tokens, const dsr::ReadableString &fromPath) {
 	if (tokens.length() > 0) {
 		bool activeLine = target.activeStackDepth >= target.currentStackDepth;
 		/*
@@ -180,7 +205,7 @@ static void interpretLine(Machine &target, List<String> &tokens, const dsr::Read
 			if (string_caseInsensitiveMatch(first, U"import")) {
 				// Get path relative to importing script's path.
 				String importPath = PATH_EXPR(1, tokens.length() - 1);
-				evaluateScript(target, importPath);
+				evaluateScript(output, context, target, importPath);
 				if (tokens.length() > 2) { printText(U"Unused tokens after import!\n");}
 			} else if (string_caseInsensitiveMatch(first, U"if")) {
 				// Being if statement
@@ -195,7 +220,16 @@ static void interpretLine(Machine &target, List<String> &tokens, const dsr::Read
 				target.activeStackDepth = target.currentStackDepth;
 			} else if (string_caseInsensitiveMatch(first, U"crawl")) {
 				// The right hand expression is evaluated into a path relative to the build script and used as the root for searching for source code.
-				analyzeSource(PATH_EXPR(1, tokens.length() - 1));
+				crawlSource(context, PATH_EXPR(1, tokens.length() - 1));
+			} else if (string_caseInsensitiveMatch(first, U"build")) {
+				// Build one or more other projects from a project file or folder path, as dependencies.
+				//   Having the same external project built twice during the same session is not allowed.
+				Machine childTarget;
+				inheritMachine(childTarget, target);
+				String projectPath = file_getTheoreticalAbsolutePath(unwrapIfNeeded(second), fromPath); // Use the second token as the folder path.
+				argumentsToSettings(childTarget, tokens, 2); // Send all tokens after the second token as input arguments to buildProjects.
+				printText("Building ", second, " from ", fromPath, " which is ", projectPath, "\n");
+				build(output, projectPath, childTarget);
 			} else if (string_caseInsensitiveMatch(first, U"link")) {
 				// Only the path name itself is needed, so any redundant -l prefixes will be stripped away.
 				String libraryName = STRING_EXPR(1, tokens.length() - 1);
@@ -212,13 +246,13 @@ static void interpretLine(Machine &target, List<String> &tokens, const dsr::Read
 			} else {
 				if (tokens.length() == 1) {
 					// Mentioning an identifier without assigning anything will assign it to one as a boolean flag.
-					assignValue(target, first, U"1");
+					assignValue(target, first, U"1", false);
 				} else if (string_match(second, U"=")) {
 					// TODO: Create in-place math and string operations with different types of assignments.
 					//       Maybe use a different syntax beginning with a keyword?
 					// TODO: Look for the assignment operator dynamically if references to collection elements are allowed as l-value expressions.
 					// Using an equality sign replaces any previous value of the variable.
-					assignValue(target, first, STRING_EXPR(2, tokens.length() - 1));
+					assignValue(target, first, STRING_EXPR(2, tokens.length() - 1), false);
 				} else {
 					// TODO: Give better error messages.
 					printText(U"  Ignored unrecognized statement!\n");
@@ -235,7 +269,7 @@ static void interpretLine(Machine &target, List<String> &tokens, const dsr::Read
 	tokens.clear();
 }
 
-void evaluateScript(Machine &target, const ReadableString &scriptPath) {
+void evaluateScript(ScriptTarget &output, ProjectContext &context, Machine &target, const ReadableString &scriptPath) {
 	if (file_getEntryType(scriptPath) != EntryType::File) {
 		printText(U"The script path ", scriptPath, U" does not exist!\n");
 	}
@@ -254,7 +288,7 @@ void evaluateScript(Machine &target, const ReadableString &scriptPath) {
 		if (c == U'\n' || c == U'\0') {
 			// Comment removing everything else.
 			flushToken(currentLine, currentToken);
-			interpretLine(target, currentLine, projectFolderPath);
+			interpretLine(output, context, target, currentLine, projectFolderPath);
 			commented = false; // Automatically end comments at end of line.
 			quoted = false; // Automatically end quotes at end of line.
 		} else if (c == U'\"') {
@@ -263,7 +297,7 @@ void evaluateScript(Machine &target, const ReadableString &scriptPath) {
 		} else if (c == U'#') {
 			// Comment removing everything else until a new line comes.
 			flushToken(currentLine, currentToken);
-			interpretLine(target, currentLine, projectFolderPath);
+			interpretLine(output, context, target, currentLine, projectFolderPath);
 			commented = true;
 		} else if (!commented) {
 			if (quoted) {
@@ -286,3 +320,87 @@ void evaluateScript(Machine &target, const ReadableString &scriptPath) {
 		}
 	}
 }
+
+static List<String> initializedProjects;
+// Using a project file path and input arguments.
+void buildProject(ScriptTarget &output, const ReadableString &projectFilePath, Machine settings) {
+	printText("Building project at ", projectFilePath, "\n");
+	// Check if this project has begun building previously during this session.
+	String absolutePath = file_getAbsolutePath(projectFilePath);
+	for (int p = 0; p < initializedProjects.length(); p++) {
+		if (string_caseInsensitiveMatch(absolutePath, initializedProjects[p])) {
+			throwError(U"Found duplicate requests to build from the same initial script ", absolutePath, U" which could cause non-determinism if different arguments are given to each!\n");
+			return;
+		}
+	}
+	// Remember that building of this project has started.
+	initializedProjects.push(absolutePath);
+	// Evaluate compiler settings while searching for source code mentioned in the project and imported headers.
+	printText(U"Executing project file from ", projectFilePath, U".\n");
+	ProjectContext context;
+	evaluateScript(output, context, settings, projectFilePath);
+	// Find out where things are located.
+	String projectPath = file_getAbsoluteParentFolder(projectFilePath);
+	// Interpret ProgramPath relative to the project path.
+	ReadableString programPath = getFlag(settings, U"ProgramPath", output.language == ScriptLanguage::Batch ? U"program.exe" : U"program");
+	programPath = file_getTheoreticalAbsolutePath(programPath, projectPath);
+	// If the SkipIfBinaryExists flag is given, we will abort as soon as we have handled its external BuildProjects requests and confirmed that the application exists.
+	if (getFlagAsInteger(settings, U"SkipIfBinaryExists") && file_getEntryType(programPath) == EntryType::File) {
+		// SkipIfBinaryExists was active and the binary exists, so abort here to avoid redundant work.
+		printText(U"Skipping build of ", projectFilePath, U" because the SkipIfBinaryExists flag was given.\n");
+		return;
+	}
+	// Once we are done finding all source files, we can resolve the dependencies to create a graph connected by indices.
+	resolveDependencies(context);
+	if (getFlagAsInteger(settings, U"ListDependencies")) {
+		printDependencies(context);
+	}
+	generateCompilationScript(output, context, settings, programPath);
+}
+
+// Using a folder path and input arguments for all projects.
+void buildProjects(ScriptTarget &output, const ReadableString &projectFolderPath, Machine &settings) {
+	printText("Building all projects in ", projectFolderPath, "\n");
+	file_getFolderContent(projectFolderPath, [&settings, &output](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
+		if (entryType == EntryType::Folder) {
+			buildProjects(output, entryPath, settings);
+		} else if (entryType == EntryType::File) {
+			ReadableString extension = string_upperCase(file_getExtension(entryName));
+			if (string_match(extension, U"DSRPROJ")) {
+				buildProject(output, entryPath, settings);
+			}
+		}
+	});
+}
+
+void build(ScriptTarget &output, const ReadableString &projectPath, Machine &settings) {
+	EntryType entryType = file_getEntryType(projectPath);
+	printText("Building anything at ", projectPath, " which is ", entryType, "\n");
+	if (entryType == EntryType::File) {
+		String extension = string_upperCase(file_getExtension(projectPath));
+		if (!string_match(extension, U"DSRPROJ")) {
+			printText(U"Can't use the Build keyword with a file that is not a project!\n");
+		} else {
+			// Build the given project
+			buildProject(output, projectPath, settings);
+		}
+	} else if (entryType == EntryType::Folder) {
+		buildProjects(output, projectPath, settings);
+	}
+}
+
+void argumentsToSettings(Machine &settings, const List<String> &arguments, int64_t firstArgument) {
+	for (int a = firstArgument; a < arguments.length(); a++) {
+		String argument = arguments[a];
+		int64_t assignmentIndex = string_findFirst(argument, U'=');
+		if (assignmentIndex == -1) {
+			assignValue(settings, argument, U"1", true);
+			printText(U"Assigning ", argument, U" to 1 from input argument.\n");
+		} else {
+			String key = string_removeOuterWhiteSpace(string_before(argument, assignmentIndex));
+			String value = string_removeOuterWhiteSpace(string_after(argument, assignmentIndex));
+			assignValue(settings, key, value, true);
+			printText(U"Assigning ", key, U" to ", value, U" from input argument.\n");
+		}
+	}
+}

+ 64 - 5
Source/tools/builder/Machine.h

@@ -7,10 +7,12 @@
 using namespace dsr;
 
 struct Flag {
+	// Flags created externally using argumentsToSettings from either the command line or another project, will be marked as inherited and given to the next call.
+	bool inherited;
 	dsr::String key, value;
 	Flag() {}
-	Flag(const dsr::ReadableString &key, const dsr::ReadableString &value)
-	: key(key), value(value) {}
+	Flag(const dsr::ReadableString &key, const dsr::ReadableString &value, bool inherited)
+	: key(key), value(value), inherited(inherited) {}
 };
 
 struct Machine {
@@ -21,6 +23,52 @@ struct Machine {
 	int64_t activeStackDepth = 0;
 };
 
+enum class Extension {
+	Unknown, H, Hpp, C, Cpp
+};
+
+enum class ScriptLanguage {
+	Unknown,
+	Batch,
+	Bash
+};
+
+struct ScriptTarget {
+	ScriptLanguage language;
+	String tempPath;
+	String generatedCode;
+	ScriptTarget(ScriptLanguage language, const ReadableString &tempPath)
+	: language(language), tempPath(tempPath) {}
+};
+
+Extension extensionFromString(const ReadableString& extensionName);
+
+struct Connection {
+	String path;
+	int64_t lineNumber = -1;
+	int64_t dependencyIndex = -1;
+	Connection(const ReadableString& path)
+	: path(path) {}
+	Connection(const ReadableString& path, int64_t lineNumber)
+	: path(path), lineNumber(lineNumber) {}
+};
+
+struct Dependency {
+	String path;
+	Extension extension;
+	uint64_t contentChecksum;
+	bool visited; // Used to avoid infinite loops while traversing dependencies.
+	List<Connection> links; // Depends on having these linked after compiling.
+	List<Connection> includes; // Depends on having these included in pre-processing.
+	Dependency(const ReadableString& path, Extension extension, uint64_t contentChecksum)
+	: path(path), extension(extension), contentChecksum(contentChecksum) {}
+};
+
+struct ProjectContext {
+	List<Dependency> dependencies;
+	ProjectContext() {}
+};
+
 // Returns the first case insensitive match for key in target, or -1 if not found.
 int64_t findFlag(const Machine &target, const dsr::ReadableString &key);
 // Returns the value of key in target, or defaultValue if not found.
@@ -29,10 +77,21 @@ ReadableString getFlag(const Machine &target, const dsr::ReadableString &key, co
 int64_t getFlagAsInteger(const Machine &target, const dsr::ReadableString &key, int64_t defaultValue = 0);
 
 // Assigns value to key in target. Allocates key in target if it does not already exist.
-void assignValue(Machine &target, const dsr::ReadableString &key, const dsr::ReadableString &value);
+void assignValue(Machine &target, const dsr::ReadableString &key, const dsr::ReadableString &value, bool inherited);
 
-// Modifies the flags in target using the script in scriptPath.
+// Modifies the flags in target, while listing source files to context, using the script in scriptPath.
 // Recursively including other scripts using the script's folder as the origin for relative paths.
-void evaluateScript(Machine &target, const ReadableString &scriptPath);
+void evaluateScript(ScriptTarget &output, ProjectContext &context, Machine &target, const ReadableString &scriptPath);
+
+// Build anything in projectPath.
+void build(ScriptTarget &output, const ReadableString &projectPath, Machine &settings);
+
+// Build the project in projectFilePath.
+// Settings must be taken by value to prevent side-effects from spilling over between different scripts.
+void buildProject(ScriptTarget &output, const ReadableString &projectFilePath, Machine settings);
+
+// Build all projects in projectFolderPath.
+void buildProjects(ScriptTarget &output, const ReadableString &projectFolderPath, Machine &settings);
+void argumentsToSettings(Machine &settings, const List<String> &arguments, int64_t firstArgument);
 
 #endif

+ 2 - 2
Source/tools/builder/buildProject.bat

@@ -1,7 +1,7 @@
 @echo off
 
 rem Using buildProject.bat
-rem   %1 must be the *.DsrProj path, which is relative to the caller location.
+rem   %1 must be the *.DsrProj path or a folder containing such projects. The path is relative to the caller location.
 rem   %2... are variable assignments sent as input to the given project file.
 
rem   CPP_COMPILER_PATH should be modified if it does not already refer to an installed C++ compiler.
 
@@ -44,7 +44,7 @@ echo Generating %SCRIPT_PATH% from %1%
 if exist %SCRIPT_PATH% (
 	del %SCRIPT_PATH%
 )
-%BUILDER_EXECUTABLE% %* ScriptPath=%SCRIPT_PATH% Compiler=%CPP_COMPILER_PATH% CompileFrom=%CPP_COMPILER_FOLDER%
+%BUILDER_EXECUTABLE% %SCRIPT_PATH% %* Compiler=%CPP_COMPILER_PATH% CompileFrom=%CPP_COMPILER_FOLDER%
 if exist %SCRIPT_PATH% (
 	echo Running %SCRIPT_PATH%
 	%SCRIPT_PATH%

+ 5 - 5
Source/tools/builder/buildProject.sh

@@ -1,7 +1,7 @@
 # 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.
-#   BUILDER_CPP_COMPILER_PATH should be modified if it does not already refer to an installed C++ compiler.
+#   CPP_COMPILER_PATH should be modified if it does not already refer to an installed C++ compiler.
 
 echo "Running buildProject.sh $@"
 
@@ -11,8 +11,8 @@ echo "BUILDER_FOLDER = ${BUILDER_FOLDER}"
 BUILDER_EXECUTABLE="${BUILDER_FOLDER}/builder"
 echo "BUILDER_EXECUTABLE = ${BUILDER_EXECUTABLE}"
 
-BUILDER_CPP_COMPILER_PATH="g++"
-echo "Change BUILDER_CPP_COMPILER_PATH in ${BUILDER_FOLDER}/buildProject.sh if you are not using ${BUILDER_CPP_COMPILER_PATH} as your compiler."
+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
@@ -21,7 +21,7 @@ else
 	echo "Building the Builder build system for first time use."
 	LIBRARY_PATH="$(realpath ${BUILDER_FOLDER}/../../DFPSR)"
 	SOURCE_CODE="${BUILDER_FOLDER}/main.cpp ${BUILDER_FOLDER}/Machine.cpp ${BUILDER_FOLDER}/generator.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"
-	"${BUILDER_CPP_COMPILER_PATH}" -o "${BUILDER_EXECUTABLE}" ${SOURCE_CODE} -std=c++14
+	"${CPP_COMPILER_PATH}" -o "${BUILDER_EXECUTABLE}" ${SOURCE_CODE} -std=c++14
 	if [ $? -eq 0 ]; then
 		echo "Completed building the Builder build system."
 	else
@@ -37,7 +37,7 @@ echo "Generating ${SCRIPT_PATH} from $1"
 if [ -e "${SCRIPT_PATH}" ]; then
 	rm "${SCRIPT_PATH}"
 fi
-"${BUILDER_EXECUTABLE}" $@ "ScriptPath=${SCRIPT_PATH}";
+"${BUILDER_EXECUTABLE}" "${SCRIPT_PATH}" "$@" "Compiler=${CPP_COMPILER_PATH}";
 if [ -e "${SCRIPT_PATH}" ]; then
 	echo "Giving execution permission to ${SCRIPT_PATH}"
 	chmod +x "${SCRIPT_PATH}"

+ 118 - 195
Source/tools/builder/generator.cpp

@@ -32,87 +32,40 @@ static uint64_t checksum(const Buffer& buffer) {
 	return d;
 }
 
-enum class ScriptLanguage {
-	Unknown,
-	Batch,
-	Bash
-};
-
-struct Connection {
-	String path;
-	int64_t lineNumber = -1;
-	int64_t dependencyIndex = -1;
-	Connection(const ReadableString& path)
-	: path(path) {}
-	Connection(const ReadableString& path, int64_t lineNumber)
-	: path(path), lineNumber(lineNumber) {}
-};
-
-enum class Extension {
-	Unknown, H, Hpp, C, Cpp
-};
-static Extension extensionFromString(const ReadableString& extensionName) {
-	String upperName = string_upperCase(string_removeOuterWhiteSpace(extensionName));
-	Extension result = Extension::Unknown;
-	if (string_match(upperName, U"H")) {
-		result = Extension::H;
-	} else if (string_match(upperName, U"HPP")) {
-		result = Extension::Hpp;
-	} else if (string_match(upperName, U"C")) {
-		result = Extension::C;
-	} else if (string_match(upperName, U"CPP")) {
-		result = Extension::Cpp;
-	}
-	return result;
-}
-
-struct Dependency {
-	String path;
-	Extension extension;
-	uint64_t contentChecksum;
-	bool visited; // Used to avoid infinite loops while traversing dependencies.
-	List<Connection> links; // Depends on having these linked after compiling.
-	List<Connection> includes; // Depends on having these included in pre-processing.
-	Dependency(const ReadableString& path, Extension extension, uint64_t contentChecksum)
-	: path(path), extension(extension), contentChecksum(contentChecksum) {}
-};
-List<Dependency> dependencies;
-
-static int64_t findDependency(const ReadableString& findPath);
+static int64_t findDependency(ProjectContext &context, const ReadableString& findPath);
 static void resolveConnection(Connection &connection);
 static void resolveDependency(Dependency &dependency);
 static String findSourceFile(const ReadableString& headerPath, bool acceptC, bool acceptCpp);
 static void flushToken(List<String> &target, String &currentToken);
 static void tokenize(List<String> &target, const ReadableString& line);
-static void interpretPreprocessing(int64_t parentIndex, const List<String> &tokens, const ReadableString &parentFolder, int64_t lineNumber);
-static void interpretPreprocessing(int64_t parentIndex, const List<String> &tokens, const ReadableString &parentFolder, int64_t lineNumber);
-static void analyzeCode(int64_t parentIndex, String content, const ReadableString &parentFolder);
+static void interpretPreprocessing(ProjectContext &context, int64_t parentIndex, const List<String> &tokens, const ReadableString &parentFolder, int64_t lineNumber);
+static void analyzeCode(ProjectContext &context, int64_t parentIndex, String content, const ReadableString &parentFolder);
 
-static int64_t findDependency(const ReadableString& findPath) {
-	for (int d = 0; d < dependencies.length(); d++) {
-		if (string_match(dependencies[d].path, findPath)) {
+static int64_t findDependency(ProjectContext &context, const ReadableString& findPath) {
+	for (int d = 0; d < context.dependencies.length(); d++) {
+		if (string_match(context.dependencies[d].path, findPath)) {
 			return d;
 		}
 	}
 	return -1;
 }
 
-static void resolveConnection(Connection &connection) {
-	connection.dependencyIndex = findDependency(connection.path);
+static void resolveConnection(ProjectContext &context, Connection &connection) {
+	connection.dependencyIndex = findDependency(context, connection.path);
 }
 
-static void resolveDependency(Dependency &dependency) {
+static void resolveDependency(ProjectContext &context, Dependency &dependency) {
 	for (int l = 0; l < dependency.links.length(); l++) {
-		resolveConnection(dependency.links[l]);
+		resolveConnection(context, dependency.links[l]);
 	}
 	for (int i = 0; i < dependency.includes.length(); i++) {
-		resolveConnection(dependency.includes[i]);
+		resolveConnection(context, dependency.includes[i]);
 	}
 }
 
-void resolveDependencies() {
-	for (int d = 0; d < dependencies.length(); d++) {
-		resolveDependency(dependencies[d]);
+void resolveDependencies(ProjectContext &context) {
+	for (int d = 0; d < context.dependencies.length(); d++) {
+		resolveDependency(context, context.dependencies[d]);
 	}
 }
 
@@ -161,24 +114,24 @@ static void tokenize(List<String> &target, const ReadableString& line) {
 	flushToken(target, currentToken);
 }
 
-static void interpretPreprocessing(int64_t parentIndex, const List<String> &tokens, const ReadableString &parentFolder, int64_t lineNumber) {
+static void interpretPreprocessing(ProjectContext &context, int64_t parentIndex, const List<String> &tokens, const ReadableString &parentFolder, int64_t lineNumber) {
 	if (tokens.length() >= 3) {
 		if (string_match(tokens[1], U"include")) {
 			if (tokens[2][0] == U'\"') {
 				String relativePath = string_unmangleQuote(tokens[2]);
 				String absolutePath = file_getTheoreticalAbsolutePath(relativePath, parentFolder, LOCAL_PATH_SYNTAX);
-				dependencies[parentIndex].includes.pushConstruct(absolutePath, lineNumber);
-				analyzeFromFile(absolutePath);
+				context.dependencies[parentIndex].includes.pushConstruct(absolutePath, lineNumber);
+				analyzeFromFile(context, absolutePath);
 			}
 		}
 	}
 }
 
-static void analyzeCode(int64_t parentIndex, String content, const ReadableString &parentFolder) {
+static void analyzeCode(ProjectContext &context, int64_t parentIndex, String content, const ReadableString &parentFolder) {
 	List<String> tokens;
 	bool continuingLine = false;
 	int64_t lineNumber = 0;
-	string_split_callback(content, U'\n', true, [&parentIndex, &parentFolder, &tokens, &continuingLine, &lineNumber](ReadableString line) {
+	string_split_callback(content, U'\n', true, [&parentIndex, &parentFolder, &tokens, &continuingLine, &lineNumber, &context](ReadableString line) {
 		lineNumber++;
 		if (line[0] == U'#' || continuingLine) {
 			tokenize(tokens, line);
@@ -188,14 +141,14 @@ static void analyzeCode(int64_t parentIndex, String content, const ReadableStrin
 			continuingLine = false;
 		}
 		if (!continuingLine && tokens.length() > 0) {
-			interpretPreprocessing(parentIndex, tokens, parentFolder, lineNumber);
+			interpretPreprocessing(context, parentIndex, tokens, parentFolder, lineNumber);
 			tokens.clear();
 		}
 	});
 }
 
-void analyzeFromFile(const ReadableString& absolutePath) {
-	if (findDependency(absolutePath) != -1) {
+void analyzeFromFile(ProjectContext &context, const ReadableString& absolutePath) {
+	if (findDependency(context, absolutePath) != -1) {
 		// Already analyzed the current entry. Abort to prevent duplicate dependencies.
 		return;
 	}
@@ -204,25 +157,25 @@ void analyzeFromFile(const ReadableString& absolutePath) {
 		Extension extension = extensionFromString(string_after(absolutePath, lastDotIndex));
 		if (extension != Extension::Unknown) {
 			// The old length will be the new dependency's index.
-			int64_t parentIndex = dependencies.length();
+			int64_t parentIndex = context.dependencies.length();
 			// Get the file's binary content.
 			Buffer fileBuffer = file_loadBuffer(absolutePath);
 			// Get the checksum
 			uint64_t contentChecksum = checksum(fileBuffer);
-			dependencies.pushConstruct(absolutePath, extension, contentChecksum);
+			context.dependencies.pushConstruct(absolutePath, extension, contentChecksum);
 			if (extension == Extension::H || extension == Extension::Hpp) {
 				// The current file is a header, so look for an implementation with the corresponding name.
 				String sourcePath = findSourceFile(absolutePath, extension == Extension::H, true);
 				// If found:
 				if (string_length(sourcePath) > 0) {
 					// Remember that anything using the header will have to link with the implementation.
-					dependencies[parentIndex].links.pushConstruct(sourcePath);
+					context.dependencies[parentIndex].links.pushConstruct(sourcePath);
 					// Look for included headers in the implementation file.
-					analyzeFromFile(sourcePath);
+					analyzeFromFile(context, sourcePath);
 				}
 			}
 			// Interpret the file's content.
-			analyzeCode(parentIndex, string_loadFromMemory(fileBuffer), file_getRelativeParentFolder(absolutePath));
+			analyzeCode(context, parentIndex, string_loadFromMemory(fileBuffer), file_getRelativeParentFolder(absolutePath));
 		}
 	}
 }
@@ -239,66 +192,54 @@ static void debugPrintDependencyList(const List<Connection> &connnections, const
 	}
 }
 
-void printDependencies() {
-	for (int d = 0; d < dependencies.length(); d++) {
-		printText(U"* ", file_getPathlessName(dependencies[d].path), U"\n");
-		debugPrintDependencyList(dependencies[d].includes, U"including");
-		debugPrintDependencyList(dependencies[d].links, U"linking");
-	}
-}
-
-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;
-	} else {
-		throwError(U"Could not identify the scripting language of ", filename, U". Use *.bat or *.sh.\n");
-		return ScriptLanguage::Unknown;
+void printDependencies(ProjectContext &context) {
+	for (int d = 0; d < context.dependencies.length(); d++) {
+		printText(U"* ", file_getPathlessName(context.dependencies[d].path), U"\n");
+		debugPrintDependencyList(context.dependencies[d].includes, U"including");
+		debugPrintDependencyList(context.dependencies[d].links, U"linking");
 	}
 }
 
-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");
+static void script_printMessage(ScriptTarget &output, const ReadableString message) {
+	if (output.language == ScriptLanguage::Batch) {
+		string_append(output.generatedCode, U"echo ", message, U"\n");
+	} else if (output.language == ScriptLanguage::Bash) {
+		string_append(output.generatedCode, U"echo ", message, U"\n");
 	}
 }
 
-static void script_executeLocalBinary(String &output, ScriptLanguage language, const ReadableString code) {
-	if (language == ScriptLanguage::Batch) {
-		string_append(output, code, ".exe\n");
-	} else if (language == ScriptLanguage::Bash) {
-		string_append(output, file_combinePaths(U".", code), U";\n");
+static void script_executeLocalBinary(ScriptTarget &output, const ReadableString code) {
+	if (output.language == ScriptLanguage::Batch) {
+		string_append(output.generatedCode, code, ".exe\n");
+	} else if (output.language == ScriptLanguage::Bash) {
+		string_append(output.generatedCode, file_combinePaths(U".", code), U";\n");
 	}
 }
 
-static void traverserHeaderChecksums(uint64_t &target, int64_t dependencyIndex) {
+static void traverserHeaderChecksums(ProjectContext &context, uint64_t &target, int64_t dependencyIndex) {
 	// Use checksums from headers
-	for (int h = 0; h < dependencies[dependencyIndex].includes.length(); h++) {
-		int64_t includedIndex = dependencies[dependencyIndex].includes[h].dependencyIndex;
-		if (!dependencies[includedIndex].visited) {
-			//printText(U"	traverserHeaderChecksums(", includedIndex, U") ", dependencies[includedIndex].path, "\n");
+	for (int h = 0; h < context.dependencies[dependencyIndex].includes.length(); h++) {
+		int64_t includedIndex = context.dependencies[dependencyIndex].includes[h].dependencyIndex;
+		if (!context.dependencies[includedIndex].visited) {
+			//printText(U"	traverserHeaderChecksums(context, ", includedIndex, U") ", context.dependencies[includedIndex].path, "\n");
 			// Bitwise exclusive or is both order independent and entropy preserving for non-repeated content.
-			target = target ^ dependencies[includedIndex].contentChecksum;
+			target = target ^ context.dependencies[includedIndex].contentChecksum;
 			// Just have to make sure that the same checksum is not used twice.
-			dependencies[includedIndex].visited = true;
+			context.dependencies[includedIndex].visited = true;
 			// Use checksums from headers recursively
-			traverserHeaderChecksums(target, includedIndex);
+			traverserHeaderChecksums(context, target, includedIndex);
 		}
 	}
 }
 
-static uint64_t getCombinedChecksum(int64_t dependencyIndex) {
-	//printText(U"getCombinedChecksum(", dependencyIndex, U") ", dependencies[dependencyIndex].path, "\n");
-	for (int d = 0; d < dependencies.length(); d++) {
-		dependencies[d].visited = false;
+static uint64_t getCombinedChecksum(ProjectContext &context, int64_t dependencyIndex) {
+	//printText(U"getCombinedChecksum(context, ", dependencyIndex, U") ", context.dependencies[dependencyIndex].path, "\n");
+	for (int d = 0; d < context.dependencies.length(); d++) {
+		context.dependencies[d].visited = false;
 	}
-	dependencies[dependencyIndex].visited = true;
-	uint64_t result = dependencies[dependencyIndex].contentChecksum;
-	traverserHeaderChecksums(result, dependencyIndex);
+	context.dependencies[dependencyIndex].visited = true;
+	uint64_t result = context.dependencies[dependencyIndex].contentChecksum;
+	traverserHeaderChecksums(context, result, dependencyIndex);
 	return result;
 }
 
@@ -306,33 +247,15 @@ 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;
-	SourceObject(const ReadableString& sourcePath, const ReadableString& tempFolder, const ReadableString& identity, int64_t dependencyIndex)
-	: identityChecksum(checksum(identity)), combinedChecksum(getCombinedChecksum(dependencyIndex)), sourcePath(sourcePath) {
+	SourceObject(ProjectContext &context, const ReadableString& sourcePath, const ReadableString& tempFolder, const ReadableString& identity, int64_t dependencyIndex)
+	: identityChecksum(checksum(identity)), combinedChecksum(getCombinedChecksum(context, dependencyIndex)), sourcePath(sourcePath) {
 		// By making the content checksum a part of the name, one can switch back to an older version without having to recompile everything again.
 		// Just need to clean the temporary folder once in a while because old versions can take a lot of space.
 		this->objectPath = file_combinePaths(tempFolder, string_combine(U"dfpsr_", this->identityChecksum, U"_", this->combinedChecksum, U".o"));
 	}
 };
 
-void generateCompilationScript(const Machine &settings, const ReadableString& projectPath) {
-	ReadableString scriptPath = getFlag(settings, U"ScriptPath", U"");
-	ReadableString tempFolder = file_getAbsoluteParentFolder(scriptPath);
-	if (string_length(scriptPath) == 0) {
-		printText(U"No script path was given, skipping script generation\n");
-		return;
-	}
-	ScriptLanguage language = identifyLanguage(scriptPath);
-	scriptPath = file_getTheoreticalAbsolutePath(scriptPath, projectPath);
-	// The compiler is often a global alias, so the user must supply either an alias or an absolute path.
-	ReadableString compilerName = getFlag(settings, U"Compiler", U"g++"); // Assume g++ as the compiler if not specified.
-	ReadableString compileFrom = getFlag(settings, U"CompileFrom", U"");
-	// Check if the build system was asked to run the compiler from a specific folder.
-	bool changePath = (string_length(compileFrom) > 0);
-	if (changePath) {
-		printText(U"Using ", compilerName, " as the compiler executed from ", compileFrom, ".\n");
-	} else {
-		printText(U"Using ", compilerName, " as the compiler from the current directory.\n");
-	}
+void generateCompilationScript(ScriptTarget &output, ProjectContext &context, const Machine &settings, ReadableString programPath) {
 	// Convert lists of linker and compiler flags into strings.
 	// TODO: Give a warning if two contradictory flags are used, such as optimization levels and language versions.
 	// TODO: Make sure that no spaces are inside of the flags, because that can mess up detection of pre-existing and contradictory arguments.
@@ -344,6 +267,26 @@ void generateCompilationScript(const Machine &settings, const ReadableString& pr
 	for (int i = 0; i < settings.linkerFlags.length(); i++) {
 		string_append(linkerFlags, " -l", settings.linkerFlags[i]);
 	}
+	printText(U"Generating build instructions for ", programPath, U" using settings:\n");
+	printText(U"  Compiler flags:", compilerFlags, U"\n");
+	printText(U"  Linker flags:", linkerFlags, U"\n");
+	for (int v = 0; v < settings.variables.length(); v++) {
+		printText(U"  * ", settings.variables[v].key, U" = ", settings.variables[v].value);
+		if (settings.variables[v].inherited) {
+			printText(U" (inherited input)");
+		}
+		printText(U"\n");
+	}
+	// The compiler is often a global alias, so the user must supply either an alias or an absolute path.
+	ReadableString compilerName = getFlag(settings, U"Compiler", U"g++"); // Assume g++ as the compiler if not specified.
+	ReadableString compileFrom = getFlag(settings, U"CompileFrom", U"");
+	// Check if the build system was asked to run the compiler from a specific folder.
+	bool changePath = (string_length(compileFrom) > 0);
+	if (changePath) {
+		printText(U"Using ", compilerName, " as the compiler executed from ", compileFrom, ".\n");
+	} else {
+		printText(U"Using ", compilerName, " as the compiler from the current directory.\n");
+	}
 	// TODO: Warn if -DNDEBUG, -DDEBUG, or optimization levels are given directly.
 	//       Using the variables instead is both more flexible by accepting input arguments
 	//       and keeping the same format to better reuse compiled objects.
@@ -360,43 +303,28 @@ void generateCompilationScript(const Machine &settings, const ReadableString& pr
 			string_append(compilerFlags, " -static -static-libgcc -static-libstdc++");
 			string_append(linkerFlags, " -static -static-libgcc -static-libstdc++");
 		} else {
-			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");
+			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");
 		}
 	} else {
-		printText(U"Building with dynamic runtime. Don't forget to bundle the C and C++ runtimes with your application!\n");
+		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");
 	}
 	ReadableString optimizationLevel = getFlag(settings, U"Optimization", U"2");
 		printText(U"Building with optimization level ", optimizationLevel, U".\n");
 	string_append(compilerFlags, " -O", optimizationLevel);
 
-
-
-	// Interpret ProgramPath relative to the project path.
-	ReadableString programPath = getFlag(settings, U"ProgramPath", language == ScriptLanguage::Batch ? U"program.exe" : U"program");
-	programPath = file_getTheoreticalAbsolutePath(programPath, projectPath);
-
-	String output;
-	if (language == ScriptLanguage::Batch) {
-		string_append(output, U"@echo off\n\n");
-	} else if (language == ScriptLanguage::Bash) {
-		string_append(output, U"#!/bin/bash\n\n");
-	} else {
-		printText(U"The type of script could not be identified for ", scriptPath, U"!\nUse *.bat for Batch or *.sh for Bash.\n");
-		return;
-	}
 	List<SourceObject> sourceObjects;
 	bool hasSourceCode = false;
 	bool needCppCompiler = false;
-	for (int d = 0; d < dependencies.length(); d++) {
-		Extension extension = dependencies[d].extension;
+	for (int d = 0; d < context.dependencies.length(); d++) {
+		Extension extension = context.dependencies[d].extension;
 		if (extension == Extension::Cpp) {
 			needCppCompiler = true;
 		}
 		if (extension == Extension::C || extension == Extension::Cpp) {
 			// Dependency paths are already absolute from the recursive search.
-			String sourcePath = dependencies[d].path;
+			String sourcePath = context.dependencies[d].path;
 			String identity = string_combine(sourcePath, compilerFlags);
-			sourceObjects.pushConstruct(sourcePath, tempFolder, identity, d);
+			sourceObjects.pushConstruct(context, sourcePath, output.tempPath, identity, d);
 			if (file_getEntryType(sourcePath) != EntryType::File) {
 				throwError(U"The source file ", sourcePath, U" could not be found!\n");
 			} else {
@@ -408,57 +336,52 @@ void generateCompilationScript(const Machine &settings, const ReadableString& pr
 		// TODO: Give a warning if a known C compiler incapable of handling C++ is given C++ source code when needCppCompiler is true.
 		if (changePath) {
 			// Go into the requested folder.
-			if (language == ScriptLanguage::Batch) {
-				string_append(output,  "pushd ", compileFrom, "\n");
-			} else if (language == ScriptLanguage::Bash) {
-				string_append(output, U"(cd ", compileFrom, ";\n");
+			if (output.language == ScriptLanguage::Batch) {
+				string_append(output.generatedCode,  "pushd ", compileFrom, "\n");
+			} else if (output.language == ScriptLanguage::Bash) {
+				string_append(output.generatedCode, U"(cd ", compileFrom, ";\n");
 			}
 		}
 		String allObjects;
 		for (int i = 0; i < sourceObjects.length(); i++) {
-			if (language == ScriptLanguage::Batch) {
-				string_append(output,  U"if exist ", sourceObjects[i].objectPath, U" (\n");
-			} else if (language == ScriptLanguage::Bash) {
-				string_append(output, U"if [ -e \"", sourceObjects[i].objectPath, U"\" ]; then\n");
+			if (output.language == ScriptLanguage::Batch) {
+				string_append(output.generatedCode,  U"if exist ", sourceObjects[i].objectPath, U" (\n");
+			} else if (output.language == ScriptLanguage::Bash) {
+				string_append(output.generatedCode, U"if [ -e \"", sourceObjects[i].objectPath, U"\" ]; then\n");
 			}
-			script_printMessage(output, language, string_combine(U"Reusing ", sourceObjects[i].sourcePath, U" ID:", sourceObjects[i].identityChecksum, U"."));
-			if (language == ScriptLanguage::Batch) {
-				string_append(output,  U") else (\n");
-			} else if (language == ScriptLanguage::Bash) {
-				string_append(output, U"else\n");
+			script_printMessage(output, string_combine(U"Reusing ", sourceObjects[i].sourcePath, U" ID:", sourceObjects[i].identityChecksum, U"."));
+			if (output.language == ScriptLanguage::Batch) {
+				string_append(output.generatedCode,  U") else (\n");
+			} else if (output.language == ScriptLanguage::Bash) {
+				string_append(output.generatedCode, U"else\n");
 			}
-			script_printMessage(output, language, string_combine(U"Compiling ", sourceObjects[i].sourcePath, U" ID:", sourceObjects[i].identityChecksum, U" with ", compilerFlags, U"."));
-			string_append(output, compilerName, compilerFlags, U" -c ", sourceObjects[i].sourcePath, U" -o ", sourceObjects[i].objectPath, U"\n");
-			if (language == ScriptLanguage::Batch) {
-				string_append(output,  ")\n");
-			} else if (language == ScriptLanguage::Bash) {
-				string_append(output, U"fi\n");
+			script_printMessage(output, string_combine(U"Compiling ", sourceObjects[i].sourcePath, U" ID:", sourceObjects[i].identityChecksum, U" with ", compilerFlags, U"."));
+			string_append(output.generatedCode, compilerName, compilerFlags, U" -c ", sourceObjects[i].sourcePath, U" -o ", sourceObjects[i].objectPath, U"\n");
+			if (output.language == ScriptLanguage::Batch) {
+				string_append(output.generatedCode,  ")\n");
+			} else if (output.language == ScriptLanguage::Bash) {
+				string_append(output.generatedCode, U"fi\n");
 			}
 			// Remember each object name for linking.
 			string_append(allObjects, U" ", sourceObjects[i].objectPath);
 		}
-		script_printMessage(output, language, string_combine(U"Linking with ", linkerFlags, U"."));
-		string_append(output, compilerName, allObjects, linkerFlags, U" -o ", programPath, U"\n");
+		script_printMessage(output, string_combine(U"Linking with ", linkerFlags, U"."));
+		string_append(output.generatedCode, compilerName, allObjects, linkerFlags, U" -o ", programPath, U"\n");
 		if (changePath) {
 			// Get back to the previous folder.
-			if (language == ScriptLanguage::Batch) {
-				string_append(output,  "popd\n");
-			} else if (language == ScriptLanguage::Bash) {
-				string_append(output, U")\n");
+			if (output.language == ScriptLanguage::Batch) {
+				string_append(output.generatedCode,  "popd\n");
+			} else if (output.language == ScriptLanguage::Bash) {
+				string_append(output.generatedCode, U")\n");
 			}
 		}
-		script_printMessage(output, language, U"Done compiling.");
-		script_printMessage(output, language, string_combine(U"Starting ", programPath));
-		script_executeLocalBinary(output, language, programPath);
-		script_printMessage(output, language, U"The program terminated.");
-		if (language == ScriptLanguage::Batch) {
-			// Windows might close the window before you have time to read the results or error messages of a CLI application, so pause at the end.
-			string_append(output, U"pause\n");
-		}
-		if (language == ScriptLanguage::Batch) {
-			string_save(scriptPath, output);
-		} else if (language == ScriptLanguage::Bash) {
-			string_save(scriptPath, output, CharacterEncoding::BOM_UTF8, LineEncoding::Lf);
+		script_printMessage(output, U"Done building.");
+		if (getFlagAsInteger(settings, U"Supressed")) {
+			script_printMessage(output, string_combine(U"Execution of ", programPath, U" was supressed using the Supressed flag."));
+		} else {
+			script_printMessage(output, string_combine(U"Starting ", programPath));
+			script_executeLocalBinary(output, programPath);
+			script_printMessage(output, U"The program terminated.");
 		}
 	} else {
 		printText("Filed to find any source code to compile.\n");

+ 4 - 15
Source/tools/builder/generator.h

@@ -8,25 +8,14 @@
 using namespace dsr;
 
 // Analyze using calls from the machine
-void analyzeFromFile(const ReadableString& entryPath);
+void analyzeFromFile(ProjectContext &context, const ReadableString& entryPath);
 // Call from main when done analyzing source files
-void resolveDependencies();
+void resolveDependencies(ProjectContext &context);
 
 // Visualize
-void printDependencies();
+void printDependencies(ProjectContext &context);
 
 // Generate
-/*
-Setting variables:
-	* ScriptPath, a path to the script to be generated and saved. The extension of the filename decides which type of code to generate.
-	* ProgramPath, a path to the application to create.
-	* Compiler, a path or global alias to the compiler.
-	* CompileFrom, from which path should the compiler be executed? Leave empty to use the current directory.
-	* Debug, 0 for release, anything else (usually 1) for debug.
-	* StaticRuntime, 0 for dynamic runtime linking, anything else (usually 1) for static runtime.
-	* Optimization, a natural integer specifying the amount of optimization to apply.
-	
-*/
-void generateCompilationScript(const Machine &settings, const ReadableString& projectPath);
+void generateCompilationScript(ScriptTarget &output, ProjectContext &context, const Machine &settings, ReadableString programPath);
 
 #endif

+ 78 - 27
Source/tools/builder/main.cpp

@@ -4,6 +4,7 @@
 //   Otherwise buildProject.sh will just see that an old version exists and use it.
 
 // TODO:
+//  * Optimize compilation of multiple projects by only generating code for compiling objects that have not already been compiled of the same version for another project.
 //  * Implement more features for the machine, such as:
 //    * Unary negation.
 //    * else and elseif cases.
@@ -15,46 +16,96 @@
 //    Post-build should be used to execute the resulting program.
 //      Optionally with variables from the build script as input arguments.
 
+/*
+Project files:
+	Syntax:
+		Precedence is not implemented for expression evaluation, so use parentheses explicitly.
+		* Assign "10" to variable x:
+			x = 10
+		* Assign "1" to variable myVariable:
+			myVariable
+		* Assign b plus c to a:
+			a = b + c
+		* Assign b minus c to a:
+			a = b - c
+		* Assign b times c to a:
+			a = b * c
+		* Assign b divided by c to a:
+			a = b / c
+		* Concatenate "hello" and " world" into "hello world" in message:
+			message = "hello" & " world"
+		* If a is less than b or c equals 3 then assign y to z:
+			if (a < b) or (c == 3)
+				z = y
+			end if
+	Commands:
+		* Build all projects in myFolder with the SkipIfBinaryExists flag in arbitrary order before continuing with compilation
+			Build "../myFolder" SkipIfBinaryExists
+		* Add file.cpp and other implementations found through includes into the list of source code to compile and link.
+			Crawl "folder/file.cpp"
+	Systems:
+		* Linux
+			Set to non-zero on Linux or similar operating systems.
+		* Windows
+			Set to non-zero on MS-Windows.
+	Variables:
+		* SkipIfBinaryExists, skips building if the binary already exists.
+		* Supressed, prevents a compiled program from running after building, which is usually given as an extra argument to Build to avoid launching all programs in a row.
+		* ProgramPath, a path to the application to create.
+		* Compiler, a path or global alias to the compiler.
+		* CompileFrom, from which path should the compiler be executed? Leave empty to use the current directory.
+		* Debug, 0 for release, anything else (usually 1) for debug.
+		* StaticRuntime, 0 for dynamic runtime linking, anything else (usually 1) for static runtime.
+		* Optimization, a natural integer specifying the amount of optimization to apply.
+*/
+
 #include "../../DFPSR/api/fileAPI.h"
 #include "generator.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;
+	} else {
+		throwError(U"Could not identify the scripting language of ", filename, U". Use *.bat or *.sh.\n");
+		return ScriptLanguage::Unknown;
+	}
+}
+
 // List dependencies for main.cpp on Linux: ./builder main.cpp --depend
 DSR_MAIN_CALLER(dsrMain)
 void dsrMain(List<String> args) {
 	if (args.length() <= 2) {
-		printText(U"To use the DFPSR build system, pass a path to the project file and the flags you want assigned before running the build script.\n");
+		printText(U"To use the DFPSR build system, pass a path to a project file or folder containing multiple projects, and the flags you want assigned before building.\n");
 	} else {
-		// Get the project file path from the first argument after the program name.
-		String projectFilePath = args[1];
-		String platform = args[2];
-		Machine settings;
-		// Begin reading input arguments after the project's path, as named integers assigned to ones.
-		// Calling builder with the extra arguments "Graphics" and "Linux" will then create and assign both variables to 1.
+		// Get the script's destination path for all projects built during the session as the first argument.
+		String outputScriptPath = args[1];
+		String tempFolder = file_getAbsoluteParentFolder(outputScriptPath);
+		// Get the first project file's path, which can then build other projects using the same generated script.
+		String projectFilePath = args[2];
+		// 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.
 		//   Avoid spaces around the equality sign, because quotes are already used for string arguments in assignments.
-		for (int a = 2; a < args.length(); a++) {
-			String argument = args[a];
-			int64_t assignmentIndex = string_findFirst(argument, U'=');
-			if (assignmentIndex == -1) {
-				assignValue(settings, argument, U"1");
-				printText(U"Assigning ", argument, U" to 1 from input argument.\n");
-			} else {
-				String key = string_removeOuterWhiteSpace(string_before(argument, assignmentIndex));
-				String value = string_removeOuterWhiteSpace(string_after(argument, assignmentIndex));
-				assignValue(settings, key, value);
-				printText(U"Assigning ", key, U" to ", value, U" from input argument.\n");
-			}
+		Machine settings;
+		argumentsToSettings(settings, args, 3);
+		// Generate a script.
+		ScriptTarget scriptTarget = ScriptTarget(identifyLanguage(outputScriptPath), tempFolder);
+		if (scriptTarget.language == ScriptLanguage::Batch) {
+			string_append(scriptTarget.generatedCode, U"@echo off\n\n");
+		} else if (scriptTarget.language == ScriptLanguage::Bash) {
+			string_append(scriptTarget.generatedCode, U"#!/bin/bash\n\n");
 		}
-		// Evaluate compiler settings while searching for source code mentioned in the project and imported headers.
-		printText(U"Executing project file from ", projectFilePath, U".\n");
-		evaluateScript(settings, projectFilePath);
-		// Once we are done finding all source files, we can resolve the dependencies to create a graph connected by indices.
-		resolveDependencies();
-		if (getFlagAsInteger(settings, U"ListDependencies")) {
-			printDependencies();
+		build(scriptTarget, projectFilePath, settings);
+		// Save the script.
+		if (scriptTarget.language == ScriptLanguage::Batch) {
+			string_save(outputScriptPath, scriptTarget.generatedCode);
+		} else if (scriptTarget.language == ScriptLanguage::Bash) {
+			string_save(outputScriptPath, scriptTarget.generatedCode, CharacterEncoding::BOM_UTF8, LineEncoding::Lf);
 		}
-		generateCompilationScript(settings, file_getAbsoluteParentFolder(projectFilePath));
 	}
 }

+ 7 - 0
Source/tools/wizard/main.DsrProj

@@ -1,6 +1,13 @@
 # This project will use both sound and graphics, so we default initialize them to 1 before evaluating the library's header.
 # The caller should provide the operating system's name (Windows, Linux)
 Message "Starting main.DsrProj\n"
+
+# The wizard application is used to launch applications, so make sure that they are built before launching the selection menu.
+Build "../../SDK" SkipIfBinaryExists Supressed
+Build "../../templates" SkipIfBinaryExists Supressed
+
+Message "Done with building the examples and continuing with building main.DsrProj\n"
+
 Graphics
 Sound
 Import "../../DFPSR/DFPSR.DsrHead"

+ 21 - 25
Source/tools/wizard/main.cpp

@@ -33,23 +33,6 @@ Begin : Panel
 		Solid = 1
 		Top = 50
 		Color = 0,0,0
-		Begin : ListBox
-			Name = "projectList"
-			Color = 190,255,190
-			Left = 90%-100
-			Right = 100%-5
-			Top = 5
-			Bottom = 100%-50
-		End
-		Begin : Button
-			Name = "buildButton"
-			Text = "Build and run"
-			Color = 190,255,190
-			Left = 90%-100
-			Right = 100%-5
-			Top = 100%-45
-			Bottom = 100%-5
-		End
 		Begin : Picture
 			Name = "previewPicture"
 			Interpolation = 1
@@ -66,13 +49,30 @@ Begin : Panel
 			Top = 70%
 			Bottom = 100%-5
 		End
+		Begin : ListBox
+			Name = "projectList"
+			Color = 190,255,190
+			Left = 90%-100
+			Right = 100%-5
+			Top = 5
+			Bottom = 100%-50
+		End
+		Begin : Button
+			Name = "launchButton"
+			Text = "Launch"
+			Color = 190,255,190
+			Left = 90%-100
+			Right = 100%-5
+			Top = 100%-45
+			Bottom = 100%-5
+		End
 	End
 End
 )QUOTE";
 
 // Visual components
 Component projectList;
-Component buildButton;
+Component launchButton;
 Component descriptionLabel;
 Component previewPicture;
 
@@ -147,13 +147,10 @@ static void findProjects(const ReadableString& folderPath) {
 
 static void selectProject(int64_t index) {
 	int oldIndex = component_getProperty_integer(projectList, U"SelectedIndex", true);
-	printText(oldIndex, U" -> ", index,  U"\n");
 	// Don't trigger new events if the selected index is already updated manually.
 	if (index != oldIndex) {
-		printText(U"Assigned ", index,  U"\n");
 		component_setProperty_integer(projectList, U"SelectedIndex", index, false);
 	}
-	printText(U"Assigning description\n");
 	component_setProperty_string(descriptionLabel, U"Text", projects[index].description);
 	component_setProperty_image(previewPicture, U"Image", projects[index].preview, false);
 }
@@ -178,7 +175,7 @@ void dsrMain(List<String> args) {
 
 	// Find components
 	projectList = window_findComponentByName(window, U"projectList");
-	buildButton = window_findComponentByName(window, U"buildButton");
+	launchButton = window_findComponentByName(window, U"launchButton");
 	descriptionLabel = window_findComponentByName(window, U"descriptionLabel");
 	previewPicture = window_findComponentByName(window, U"previewPicture");
 
@@ -197,13 +194,12 @@ void dsrMain(List<String> args) {
 			}
 		}
 	});
-	component_setPressedEvent(buildButton, []() {
+	component_setPressedEvent(launchButton, []() {
 		// TODO: Implement building and running of the selected project.
 		playSound(boomSound, false, 1.0, 1.0, 0.7);
-		component_setProperty_string(descriptionLabel, U"Text", U"Compiling and running projects from the wizard application is not yet implemented.");
+		component_setProperty_string(descriptionLabel, U"Text", U"Running projects from the wizard application is not yet implemented.");
 	});
 	component_setSelectEvent(projectList, [](int64_t index) {
-		printText(U"Selecting ", index, U"\n");
 		playSound(boomSound, false, 0.5, 0.5, 0.5);
 		selectProject(index);
 	});