From b891fb3fe60e7b9a24e969d25fbccd71326b1ac4 Mon Sep 17 00:00:00 2001 From: David Piuva Date: Sun, 2 Feb 2025 20:31:13 +0100 Subject: [PATCH] Upgraded the Builder build system to compile regression tests and made a program for running the tests with multiple threads. --- .github/workflows/ci.yml | 38 ++++- .github/workflows/ci.yml.tabs | 38 ++++- Doc/Generator/Input/Starting.txt | 9 +- Doc/Generator/Input/StyleGuide.txt | 27 ++-- Doc/Starting.html | 10 +- Doc/StyleGuide.html | 61 +++++--- Source/check.sh | 1 - Source/test.bat | 20 --- Source/test/Tests.DsrProj | 25 ++++ Source/{ => test}/test.sh | 6 +- Source/test/testCaller.cpp | 124 ++++++++++++++++ Source/test/testTools.h | 2 +- Source/test/test_linux.sh | 4 + Source/test/test_macos.sh | 4 + Source/test/test_windows.bat | 3 + Source/test/tests/SafePointerTest.cpp | 3 +- Source/test/tests/TextEncodingTest.cpp | 2 +- Source/tools/builder/buildProject.bat | 48 +++++- Source/tools/builder/buildProject.sh | 27 +++- Source/tools/builder/code/Machine.cpp | 123 +++++++++++++++- Source/tools/builder/code/Machine.h | 2 + Source/tools/builder/code/analyzer.cpp | 138 ++++++++++++++---- Source/tools/builder/code/analyzer.h | 9 +- Source/tools/builder/code/builderTypes.h | 14 +- Source/tools/builder/code/main.cpp | 38 +++-- Source/tools/builder/compilerPaths_posix.txt | 6 + .../tools/builder/compilerPaths_windows.txt | 44 ++++++ Source/tools/wizard/main.cpp | 1 - 28 files changed, 676 insertions(+), 151 deletions(-) delete mode 100755 Source/test.bat create mode 100644 Source/test/Tests.DsrProj rename Source/{ => test}/test.sh (93%) create mode 100644 Source/test/testCaller.cpp create mode 100755 Source/test/test_linux.sh create mode 100755 Source/test/test_macos.sh create mode 100755 Source/test/test_windows.bat create mode 100644 Source/tools/builder/compilerPaths_posix.txt create mode 100644 Source/tools/builder/compilerPaths_windows.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c86cbc3..6c40e40f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,22 +3,46 @@ name: DFPSR tests on: [push] jobs: - test: + scriptedTest: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest] architecture: [x86_32, x86_64, arm, arm64] steps: - name: Checkout uses: actions/checkout@v4 - - name: Run tests on Linux and macOS - if: matrix.os != 'windows-latest' + - name: Run tests on Linux + if: matrix.os == 'ubuntu-latest' + run: | + cd ./Source/test + ./test.sh + - name: Run tests on MacOS + if: matrix.os == 'macos-latest' run: | - cd ./Source + cd ./Source/test ./test.sh + builderTest: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + architecture: [x86_32, x86_64, arm, arm64] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run tests on Linux + if: matrix.os == 'ubuntu-latest' + run: | + cd ./Source/test + ./test_linux.sh + - name: Run tests on MacOS + if: matrix.os == 'macos-latest' + run: | + cd ./Source/test + ./test_macos.sh - name: Run tests on Windows if: matrix.os == 'windows-latest' run: | - cd Source - cmd /c test.bat + cd Source\test + cmd /c test_windows.bat diff --git a/.github/workflows/ci.yml.tabs b/.github/workflows/ci.yml.tabs index a113b76d..5a11b459 100644 --- a/.github/workflows/ci.yml.tabs +++ b/.github/workflows/ci.yml.tabs @@ -2,22 +2,46 @@ name: DFPSR tests on: [push] jobs: - test: + scriptedTest: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest] architecture: [x86_32, x86_64, arm, arm64] steps: - name: Checkout uses: actions/checkout@v4 - - name: Run tests on Linux and macOS - if: matrix.os != 'windows-latest' + - name: Run tests on Linux + if: matrix.os == 'ubuntu-latest' + run: | + cd ./Source/test + ./test.sh + - name: Run tests on MacOS + if: matrix.os == 'macos-latest' run: | - cd ./Source + cd ./Source/test ./test.sh + builderTest: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + architecture: [x86_32, x86_64, arm, arm64] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run tests on Linux + if: matrix.os == 'ubuntu-latest' + run: | + cd ./Source/test + ./test_linux.sh + - name: Run tests on MacOS + if: matrix.os == 'macos-latest' + run: | + cd ./Source/test + ./test_macos.sh - name: Run tests on Windows if: matrix.os == 'windows-latest' run: | - cd Source - cmd /c test.bat + cd Source\test + cmd /c test_windows.bat diff --git a/Doc/Generator/Input/Starting.txt b/Doc/Generator/Input/Starting.txt index 62b703f0..e2820250 100644 --- a/Doc/Generator/Input/Starting.txt +++ b/Doc/Generator/Input/Starting.txt @@ -45,7 +45,8 @@ sudo apt install libx11-dev sudo apt install libasound2-dev * -Check that you have g++ installed at /usr/bin/g++ and change CPP_COMPILER_PATH in Source/tools/builder/buildProject.sh if it's located somewhere else. +Check that you have g++ installed at /usr/bin/g++. +If it is located in a different path, then add the path to the top of compilerPaths_posix.txt. You can also change TEMPORARY_FOLDER if using another system than Linux where /tmp is called something else, or you want to keep objects in a persistent folder after your system reboots. The advantage of using /tmp, is that the files there can be placed in memory and you don't have to erase them manually. @@ -67,10 +68,8 @@ Download a mingw edition of CodeBlocks from their website.

-Check that you have g++ installed at /usr/bin/g++ and change CPP_COMPILER_PATH in Source/tools/builder/buildProject.sh if it's located somewhere else. +Check that you have g++ installed at /usr/bin/g++. +If it is located in a different path, then add the path to the top of compilerPaths_posix.txt. You can also change TEMPORARY_FOLDER if using another system than Linux where /tmp is called something else, or you want to keep objects in a persistent folder after your system reboots. The advantage of using /tmp, is that the files there can be placed in memory and you don't have to erase them manually. @@ -105,11 +106,8 @@

-Open Source\tools\builder\buildProject.bat in a text editor. - -

- -Check that CPP_COMPILER_FOLDER and CPP_COMPILER_PATH refers to where your GNU C++ compiler is installed. If you installed CodeBlocks in the default path C:\Program, this should usually be the case. Otherwise just update the path. +Open Source\tools\builder\compilerPaths_windows.txt in a text editor and add your installation path for the g++ compiler at at the top of the line-break separated list. +Do not leave any empty lines in between, because empty lines are used to stop the search for compilers.

diff --git a/Doc/StyleGuide.html b/Doc/StyleGuide.html index 4ab48906..e858956c 100644 --- a/Doc/StyleGuide.html +++ b/Doc/StyleGuide.html @@ -28,44 +28,57 @@

To keep the style consistent, the style being used in the library is explained in this document.

-1. Use common sense! If it looks wrong to human readers then it's wrong. Don't defeat the purpose of any rule by taking it too far. +Tabs for indentation then spaces for alignment. It's the best of both worlds by both having variable length tabs and correct alignment that works between lines of the same indentation.

-2. Don't use iterators when there is any other way to accomplish the task. You can't write efficient algorithms without knowing the data structures. -

-3. Tabs for indentation then spaces for alignment. It's the best of both worlds by both having variable length tabs and correct alignment that works between lines of the same indentation. -

-4. No dangling else, use explicit {} for safety. Otherwise someone might add an extra statement and get random crashes. +No new line for opening brackets. +Makes the code more compact and decreases the risk of copy-paste errors.

-5. No hpp extensions, use h for all headers. Could be either way, but this library uses *.h for compact naming, so keep it consistent. +No hpp extensions, use h for all headers. Could be either way, but this library uses *.h for compact naming, so keep it consistent.

-6. C-style casting for raw data manipulation and C++-style for high-level classes. +C-style casting for raw data manipulation and C++-style for high-level classes when possible. When using assembly intrinsics and raw pointer manipulation to alter the state of bits, verbose high-level abstractions only make it harder to count CPU cycles in your head. Always use the tool that makes sense for the problem you're trying to solve. C++ style is for things that are abstracted on a higher level. C style is for when a byte is just a byte and you just want to manipulate it in a specific way.

-7. Don't call member methods with "this" set to nullptr. -This would be undefined behavior and may randomly crash. -Use global functions instead. They allow checking pointers for null -because they are explicit arguments declared by the programmer. +Follow the most relevant standard without making contemporary assumptions. +For code not intended for a specific system, follow the C++ standard. +For code targeting a certain hardware using intrinsic functions, follow the hardware's standard. +For code targeting a certain operating system, follow the operating system's standard.

-8. Avoid using STD/STL directly in SDK examples. -Exposing types from the standard library should be done using an alias or wrapper in the dsr namespace. -This allow replacing the standard library without breaking backward compatibility. -The C++ standard libraries have broken backward compatibility before and it can happen again. +Do not assume that a type has a certain size or format unless it is specified explicitly. +The int type is not always 32 bits, so only use when 16 bits are enough, use int32_t for a signed 32-bit integer. +Fixed integers such as uint8_t, uint16_t, uint32_t, uint64_t, int32_t and int64_t are preferred. +For bit manipulation, use unsigned integers to avoid depending on two's complement. +The char type is usually 8 bits large, but it is not specified by the C++ standard, so use uint8_t instead for buffers and DsrChar for 32-bit Unicode characters. +The float type does not have to be any of the IEEE standards according to the C++ standard, but you can assume properties that are specified in a relevant standard. +std::string is not used, because it has an undefined character encoding, so use dsr::String or dsr::ReadableString with UTF-32 instead. +char* should only be used for constant string literals and interfacing with external libraries.

-9. Don't abuse the auto keyword everywhere just to make it look more "modern". -Explicit type safety is what makes compiled languages safer than scripting. +The code should work for both little-endian and big-endian, because both still exist. +You may however ignore mixed-endian.

-10. No new line for opening brackets. -Makes the code more compact and decreases the risk of copy-paste errors. +Do not call member methods with "this" set to null, because that is undefined behavior. +

+Leave an empty line at the end of each source document. +Even though the C++ standard tells compilers to ignore line breaks as white space during parsing, white space is still used to separate the tokens that are used. +A future C++ compiler might be designed to allow interactive input directly from the developer and ignore end of file for consistent behavior between command line input and source files. +So without a line break at the end, the last token in a cpp file may be ignored on some compilers. +

+Avoid mixing side-effects with expressions for determinism across compilers. +Non-deterministic expressions such as ((x++ - ++x) * x--) should never be used, so use ++ and -- in separate statements. +Side-effects within the same depth of an expressions may be evaluated in any order because it is not specified in C++. +Checking the return value of a function with side-effects is okay, because the side effect always come before returning the result in the called function. +Lazy evaluation such as x != nullptr && foo(x) is okay, because lazy evaluation is well specified as only evaluating the right hand side when needed. +Call chaining such as constructor(args).setSomeValue(value).setSomeOtherValue(value) is okay, because the execution order is explicit from differences in expression depth.

-11. Don't fix the style of someone else's code if you can easily read it. -Especially if there's no style rule explicitly supporting the change. -Otherwise style changes will defeat the purpose by introducing more version conflicts. +Use the std library as little as possible. +Each compiler, operating system and standard library implementation has subtle differences in how things work, which can cause programs to break on another computer. +The goal of this framework is to make things more consistent across platforms, so that code that works on one computer is more likely to work on another computer.

-12. Don't change things that you don't know how to test. +Don't over-use the auto keyword. +Spelling out the type explicitly makes the code easier to read.

diff --git a/Source/check.sh b/Source/check.sh index ca374e0f..5cf1a4f3 100644 --- a/Source/check.sh +++ b/Source/check.sh @@ -1,2 +1 @@ cppcheck . --enable=all - diff --git a/Source/test.bat b/Source/test.bat deleted file mode 100755 index 0a715627..00000000 --- a/Source/test.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -set ROOT_PATH=%CD% -set TEMP_ROOT=%ROOT_PATH%\..\..\temporary -set CPP_VERSION="-std=c++14" -set MODE="-DDEBUG" -set DEBUGGER="-g" -set SIMD="-march=native" -set O_LEVEL="-O2" - -echo ROOT_PATH is %ROOT_PATH% -echo TEMP_ROOT is %TEMP_ROOT% -echo CPP_VERSION is %CPP_VERSION% -echo MODE is %MODE% -echo DEBUGGER is %DEBUGGER% -echo SIMD is %SIMD% -echo O_LEVEL is %O_LEVEL% - -endlocal diff --git a/Source/test/Tests.DsrProj b/Source/test/Tests.DsrProj new file mode 100644 index 00000000..d0228a73 --- /dev/null +++ b/Source/test/Tests.DsrProj @@ -0,0 +1,25 @@ +# Flags here will be inherited by each test. +CompilerFlag "-std=c++14" +Debug +Supressed = 1 +#Graphics +#Sound +Import "../DFPSR/DFPSR.DsrHead" + +# Compile and run each source file ending with Test.cpp in tests as its own project. +# All settings are inherited from the caller when using source files as projects. +Projects from "*Test.cpp" in "tests" + +# TODO: +# * Allow creating scopes for temporary settings, so that a stack keeps track of which +# settings were local to the scope and should be erased when leaving the scope. +# * Or just make a method for clearing all local settings while keeping external settings such as target platform. + +# Flags here will not be inherited by the tests. +Supressed = 0 + +# Enable to run faster by skipping compilation of testCaller when it already exists. +#SkipIfBinaryExists + +# Compile the program that will run all the tests +Crawl "testCaller.cpp" diff --git a/Source/test.sh b/Source/test/test.sh similarity index 93% rename from Source/test.sh rename to Source/test/test.sh index 7778459e..03499d54 100755 --- a/Source/test.sh +++ b/Source/test/test.sh @@ -1,6 +1,6 @@ #!/bin/bash -ROOT_PATH=. +ROOT_PATH=.. TEMP_ROOT=${ROOT_PATH}/../../temporary CPP_VERSION=-std=c++14 MODE="-DDEBUG" @@ -8,7 +8,7 @@ DEBUGGER="-g" SIMD="-march=native" O_LEVEL=-O2 -chmod +x ${ROOT_PATH}/tools/build.sh; +chmod +x ${ROOT_PATH}/tools/buildScripts/build.sh; ${ROOT_PATH}/tools/buildScripts/build.sh "NONE" "NONE" "${ROOT_PATH}" "${TEMP_ROOT}" "NONE" "${MODE} ${DEBUGGER} ${SIMD} ${CPP_VERSION} ${O_LEVEL}"; if [ $? -ne 0 ] then @@ -21,7 +21,7 @@ TEMP_SUB=$(echo $TEMP_SUB | tr "+" "p") TEMP_SUB=$(echo $TEMP_SUB | tr -d " =-") TEMP_DIR=${TEMP_ROOT}/${TEMP_SUB} -for file in ./test/tests/*.cpp; do +for file in ./tests/*.cpp; do [ -e $file ] || continue # Get name without path name=${file##*/}; diff --git a/Source/test/testCaller.cpp b/Source/test/testCaller.cpp new file mode 100644 index 00000000..a432fd2c --- /dev/null +++ b/Source/test/testCaller.cpp @@ -0,0 +1,124 @@ + +// A program for calling the compiled tests. + +// TODO: +// * Check which tests in the folder have both a binary to execute and a source file with the same name. +// * Run the tests one after another and keep track of which tests failed. +// If any test failed, throw an error to exit with a non-zero value. +// Adjust how it behaves using command line arguments. +// * Allow creating a window and selecting which tests to run using a flag from the command line. + +#include "../DFPSR/includeEssentials.h" +// TODO: Should timeAPI move to essentials when used by heap.cpp to wait for threads anyway? +#include "../DFPSR/api/timeAPI.h" +#include "../DFPSR/base/threading.h" +#include "../DFPSR/base/noSimd.h" + +using namespace dsr; + +enum class TestResult { + None, // Skipped or not yet executed. + Passed, // Passed the test. + Failed // Crashed or just failed the test. +}; + +struct CompiledTest { + String programPath; + String name; + DsrProcess process; + TestResult result; + CompiledTest(const ReadableString &programPath) + : programPath(programPath), name(file_getExtensionless(file_getPathlessName(programPath))), result(TestResult::None) {} +}; + +void findCompiledTests(List &target, const ReadableString &folderPath) { + printText(U"Finding tests in ", folderPath, U"\n"); + file_getFolderContent(folderPath, [&target](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) { + if (entryType == EntryType::Folder) { + findCompiledTests(target, entryPath); + } else if (entryType == EntryType::File) { + ReadableString extension = file_getExtension(entryName); + if (string_caseInsensitiveMatch(extension, U"C") || string_caseInsensitiveMatch(extension, U"CPP")) { + String programPath = file_getExtensionless(entryPath); + #if defined(USE_MICROSOFT_WINDOWS) + programPath = programPath + ".exe"; + #endif + if (file_getEntryType(programPath) == EntryType::File) { + target.pushConstruct(programPath); + } + } + } + }); +} + +static void startTest(CompiledTest &test, const List &arguments) { + // Print each external call in the terminal for easy debugging when something goes wrong. + if (arguments.length() > 0) { + printText(U"Running test ", test.programPath, U" with"); + for (int64_t a = 0; a < arguments.length(); a++) { + printText(U" ", arguments[a]); + } + printText(U"\n"); + } else { + printText(U"Running test ", test.programPath, U"\n"); + } + if (file_getEntryType(test.programPath) != EntryType::File) { + throwError(U"Failed to execute ", test.programPath, U", because the executable file was not found!\n"); + } else { + test.process = process_execute(test.programPath, arguments); + } +} + +DSR_MAIN_CALLER(dsrMain) +void dsrMain(List args) { + printText(U"Starting test runner.\n"); + List tests; + findCompiledTests(tests, U"tests"); + printText(tests.length(), U" tests to run:\n"); + for (int64_t t = 0; t < tests.length(); t++) { + printText(U"* ", tests[t].name, U"\n"); + } + int32_t workerCount = max(1, getThreadCount() - 1); + int32_t finishedTestCount = 0; + int32_t startedTestCount = 0; + + // TODO: Give settings to the compiled tests. + // Tell the tests which file they should print everything to a string that is saved once completed or crashing. + List testArguments; + + int32_t passedCount = 0; + int32_t failedCount = 0; + + while (finishedTestCount < tests.length()) { + // Start new tests. + if (startedTestCount - finishedTestCount < workerCount && startedTestCount < tests.length()) { + startTest(tests[startedTestCount], testArguments); + startedTestCount++; + } + // Wait for tests to finish. + if (startedTestCount > finishedTestCount) { + DsrProcessStatus status = process_getStatus(tests[finishedTestCount].process); + if (status == DsrProcessStatus::Completed) { + printText(U"Passed test #", finishedTestCount, U".\n"); + tests[finishedTestCount].result = TestResult::Passed; + passedCount++; + finishedTestCount++; + } else if (status == DsrProcessStatus::Crashed) { + printText(U"Failed test #", finishedTestCount, U"!\n"); + tests[finishedTestCount].result = TestResult::Failed; + failedCount++; + finishedTestCount++; + } + // Wait for a while to let the main thread respond to system interrupts while other cores are running tests. + time_sleepSeconds(0.1); + } + } + // TODO: Print a summary of test results, even if tests were aborted by a signal. + if (passedCount == tests.length() && failedCount == 0) { + printText(U"All tests passed!\n"); + } else if (passedCount + failedCount == tests.length()) { + throwError(U"Failed tests!\n"); + } else { + throwError(U"Aborted tests!\n"); + } +} diff --git a/Source/test/testTools.h b/Source/test/testTools.h index faf5c3c5..a40b933a 100644 --- a/Source/test/testTools.h +++ b/Source/test/testTools.h @@ -1,6 +1,7 @@ #ifndef TEST_TOOLS #define TEST_TOOLS +// TODO: Make it faster to crawl source by only including what is needed by the test. #include "../DFPSR/includeFramework.h" #include @@ -142,4 +143,3 @@ const dsr::String inputPath = dsr::string_combine(U"test", file_separator(), U"i const dsr::String expectedPath = dsr::string_combine(U"test", file_separator(), U"expected", file_separator()); #endif - diff --git a/Source/test/test_linux.sh b/Source/test/test_linux.sh new file mode 100755 index 00000000..adb51dfe --- /dev/null +++ b/Source/test/test_linux.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +chmod +x ../tools/builder/buildProject.sh; +../tools/builder/buildProject.sh Tests.DsrProj Linux $@; diff --git a/Source/test/test_macos.sh b/Source/test/test_macos.sh new file mode 100755 index 00000000..026083d2 --- /dev/null +++ b/Source/test/test_macos.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +chmod +x ../tools/builder/buildProject.sh; +../tools/builder/buildProject.sh Tests.DsrProj MacOS $@; diff --git a/Source/test/test_windows.bat b/Source/test/test_windows.bat new file mode 100755 index 00000000..619baebd --- /dev/null +++ b/Source/test/test_windows.bat @@ -0,0 +1,3 @@ +@echo off + +..\tools\builder\buildProject.bat Tests.DsrProj Windows %@% diff --git a/Source/test/tests/SafePointerTest.cpp b/Source/test/tests/SafePointerTest.cpp index 946df12f..5d5415df 100644 --- a/Source/test/tests/SafePointerTest.cpp +++ b/Source/test/tests/SafePointerTest.cpp @@ -47,7 +47,6 @@ START_TEST(SafePointer) #endif } #ifndef SAFE_POINTER_CHECKS - printf("WARNING! SafePointer test ran without bound checks enabled.\n"); + #error "ERROR! SafePointer test ran without bound checks enabled!\n" #endif END_TEST - diff --git a/Source/test/tests/TextEncodingTest.cpp b/Source/test/tests/TextEncodingTest.cpp index 6bf2bed1..a7e30fe7 100644 --- a/Source/test/tests/TextEncodingTest.cpp +++ b/Source/test/tests/TextEncodingTest.cpp @@ -104,7 +104,7 @@ void compareCharacterCodes(String textA, String textB) { } START_TEST(TextEncoding) - String folderPath = string_combine(U"test", file_separator(), U"tests", file_separator(), U"resources", file_separator()); + String folderPath = string_combine(U"tests", file_separator(), U"resources", file_separator()); { // Text encodings stored in memory // Run these tests for all line encodings for (int l = 0; l <= 1; l++) { diff --git a/Source/tools/builder/buildProject.bat b/Source/tools/builder/buildProject.bat index 301648e8..4f5f71e1 100644 --- a/Source/tools/builder/buildProject.bat +++ b/Source/tools/builder/buildProject.bat @@ -1,22 +1,56 @@ +@echo off 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 Add your common installation paths for the compiler at the top of compilerPaths_windows.txt to find your preferred compiler first. rem Change the temporary folder if want generated scripts and objects to go somewhere else. set TEMPORARY_FOLDER=%TEMP% +rem Get this script's folder from the program argument, so that we can find the list of compilers. +set BUILDER_FOLDER=!0! +:loop_start_get_builder_folder + set "lastChar=!BUILDER_FOLDER:~-1!" + if "!lastChar!"=="" goto loop_end_get_builder_folder + if "!lastChar!"=="\" goto loop_end_get_builder_folder + set "BUILDER_FOLDER=!BUILDER_FOLDER:~0,-1!" + goto loop_start_get_builder_folder +:loop_end_get_builder_folder +echo BUILDER_FOLDER = !BUILDER_FOLDER! +rem Look for the compiler at the paths specified in a line-break separated list from compilerPaths_posix.txt. +set COMPILER_PATHS_FILE=compilerPaths_windows.txt +:find_compiler + rem Read a path from a line in the file. + set /p CPP_COMPILER_PATH=<%COMPILER_PATHS_FILE% + rem If we consumed all paths, then we failed to find the compiler. + if not "%CPP_COMPILER_PATH%"=="" goto no_compiler_found + echo Searching for a compiler at !CPP_COMPILER_PATH! + rem If the compiler exists at the path, then we are done. + if exist "%CPP_COMPILER_PATH%" goto found_a_compiler + rem Otherwise we repeat the loop. + goto find_compiler +:no_compiler_found + echo Could not find any compiler to build the project! Add a path to your preferred compiler at the top of %INPUT_FILE%. + exit /b 1 +:found_a_compiler +echo Found a compiler at %CPP_COMPILER_PATH% +echo Add a path to your preferred compiler at the top of %COMPILER_PATHS_FILE% if you do not want to compile using %CPP_COMPILER_PATH%. - - -@echo off +rem Get the compiler's folder from the compiler's path. +set CPP_COMPILER_FOLDER=!CPP_COMPILER_PATH! +:loop_start_get_compiler_folder + set "lastChar=!CPP_COMPILER_FOLDER:~-1!" + if "!lastChar!"=="" goto loop_end_get_compiler_folder + if "!lastChar!"=="\" goto loop_end_get_compiler_folder + set "CPP_COMPILER_FOLDER=!CPP_COMPILER_FOLDER:~0,-1!" + goto loop_start_get_compiler_folder +:loop_end_get_compiler_folder +echo CPP_COMPILER_PATH = !CPP_COMPILER_PATH! +echo CPP_COMPILER_FOLDER = !CPP_COMPILER_FOLDER! rem Using buildProject.bat 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. echo Running buildProject.bat %* diff --git a/Source/tools/builder/buildProject.sh b/Source/tools/builder/buildProject.sh index d69a8d0e..74aae16a 100755 --- a/Source/tools/builder/buildProject.sh +++ b/Source/tools/builder/buildProject.sh @@ -1,17 +1,30 @@ +#!/bin/bash # Local build settings that should be configured before building for the first time. # Make sure to erase all objects in the temporary folder before changing compiler. -# Compile using GCC's C++ compiler -CPP_COMPILER_PATH="/usr/bin/g++" - -# Compile using CLANG (Needs a 'Link "stdc++"' command in each project) -#CPP_COMPILER_PATH="/usr/bin/clang" +# Get the script's folder +BUILDER_FOLDER=$(dirname "$0") +echo "BUILDER_FOLDER = ${BUILDER_FOLDER}" -echo "Change CPP_COMPILER_PATH in ${BUILDER_FOLDER}/buildProject.sh if you are not using ${CPP_COMPILER_PATH} as your compiler." +# Look for the compiler at the paths specified in a line-break separated list from compilerPaths_posix.txt. +CPP_COMPILER_PATH="" +while IFS= read -r path; do + if [ -e "$path" ]; then + CPP_COMPILER_PATH="$path" + break # Break the loop if the path is found + fi +done < "${BUILDER_FOLDER}/compilerPaths_posix.txt" +if [ -n "$CPP_COMPILER_PATH" ]; then + echo "Found a compiler at ${CPP_COMPILER_PATH}" +else + echo "Could not find any compiler to build the project! Add a path to your preferred compiler at the top of ${BUILDER_FOLDER}/compilerPaths.txt." + exit 1 +fi +echo "Add a path to your preferred compiler at the top of ${BUILDER_FOLDER}/compilerPaths.txt if you do not want to compile using ${CPP_COMPILER_PATH}." -# 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. +# Change TEMPORARY_FOLDER if you do not 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.) diff --git a/Source/tools/builder/code/Machine.cpp b/Source/tools/builder/code/Machine.cpp index 8aafc2cc..8612e7f2 100644 --- a/Source/tools/builder/code/Machine.cpp +++ b/Source/tools/builder/code/Machine.cpp @@ -33,6 +33,9 @@ static bool isUnique(const List &list) { void printSettings(const Machine &settings) { printText(U" Project name: ", settings.projectName, U"\n"); + for (int64_t i = 0; i < settings.crawlOrigins.length(); i++) { + printText(U" Crawl origins ", settings.crawlOrigins[i], U"\n"); + } for (int64_t i = 0; i < settings.compilerFlags.length(); i++) { printText(U" Compiler flag ", settings.compilerFlags[i], U"\n"); } @@ -114,14 +117,30 @@ static String evaluateExpression(Machine &target, const List &tokens, in // Copy inherited variables from parent to child. void inheritMachine(Machine &child, const Machine &parent) { + // Only take selected variables, such as the target platform's name. for (int64_t 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]); } } } +void cloneMachine(Machine &child, const Machine &parent) { + // Inherit everything. + for (int64_t v = 0; v < parent.variables.length(); v++) { + child.variables.push(parent.variables[v]); + } + for (int64_t v = 0; v < parent.compilerFlags.length(); v++) { + child.compilerFlags.push(parent.compilerFlags[v]); + } + for (int64_t v = 0; v < parent.linkerFlags.length(); v++) { + child.linkerFlags.push(parent.linkerFlags[v]); + } + for (int64_t v = 0; v < parent.crawlOrigins.length(); v++) { + child.crawlOrigins.push(parent.crawlOrigins[v]); + } +} + static bool validIdentifier(const dsr::ReadableString &identifier) { DsrChar first = identifier[0]; if (!((U'a' <= first && first <= U'z') || (U'A' <= first && first <= U'Z'))) { @@ -136,6 +155,67 @@ static bool validIdentifier(const dsr::ReadableString &identifier) { return true; } +using NameFilter = std::function; +static NameFilter generateFilterFromPattern(const dsr::ReadableString &pattern) { + int64_t firstStar = string_findFirst(pattern, U'*'); + int64_t lastStar = string_findLast(pattern, U'*'); + if (firstStar == -1) { + return [pattern](const ReadableString &filename) -> bool { + return string_caseInsensitiveMatch(filename, pattern); + }; + } else if (firstStar == lastStar) { + String prefix = string_before(pattern, firstStar); + String postfix = string_after(pattern, lastStar); + int64_t preLength = string_length(prefix); + int64_t postLength = string_length(postfix); + int64_t minimumLength = preLength + postLength; + return [prefix, postfix, preLength, postLength, minimumLength](const ReadableString &filename) -> bool { + int64_t nameLength = string_length(filename); + if (nameLength < minimumLength) { + return false; + } else { + ReadableString foundPrefix = string_before(filename, preLength); + ReadableString foundPostfix = string_from(filename, nameLength - postLength); + return string_caseInsensitiveMatch(foundPrefix, prefix) && string_caseInsensitiveMatch(foundPostfix, postfix); + } + }; + } else { + throwError(U"Can not use '", pattern, "' as a name pattern, because the matching expression may not use more than one '*' character!\n"); + return [](const ReadableString &filename) -> bool { + return false; + }; + } +} + +static void findFiles(const dsr::ReadableString &inPath, NameFilter filter, std::function action) { + if (!file_getFolderContent(inPath, [&filter, &action](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) { + if (entryType == EntryType::File) { + if (filter(entryName)) { + action(entryPath); + } + } else if (entryType == EntryType::Folder) { + findFiles(entryPath, filter, action); + } + })) { + printText("Failed to look for files in '", inPath, "'\n"); + } +} + +static void findFilesAsProjects(Machine &target, const dsr::ReadableString &inPath, const dsr::ReadableString &fromPattern) { + printText(U"findFilesAsProjects: Looking for ", fromPattern, U" in ", inPath, U".\n"); + validateSettings(target, U"in the parent about to create projects from files"); + findFiles(inPath, generateFilterFromPattern(fromPattern), [&target](const ReadableString &path) { + printText(U"Creating a temporary project for ", path, U"\n"); + // List the file as a project. + target.projectFromSourceFilenames.push(path); + Machine allInputFlags(file_getPathlessName(path)); + cloneMachine(allInputFlags, target); + target.projectFromSourceSettings.push(allInputFlags); + }); +} + +// TODO: Improve error messages with line numbers and quoted content instead of just throwing errors. + static void interpretLine(Machine &target, const List &tokens, int64_t startTokenIndex, int64_t endTokenIndex, const dsr::ReadableString &fromPath) { // Automatically clamp to safe bounds. if (startTokenIndex < 0) startTokenIndex = 0; @@ -176,6 +256,46 @@ static void interpretLine(Machine &target, const List &tokens, int64_t s // The right hand expression is evaluated into a path relative to the build script and used as the root for searching for source code. target.crawlOrigins.push(PATH_EXPR(startTokenIndex + 1, endTokenIndex)); validateSettings(target, U"in target after listing a crawl origin\n"); + } else if (string_caseInsensitiveMatch(first, U"projects")) { + // TODO: Should it be possible to give the string content of variables as patterns and paths? + //Projects from "*Test.cpp" in "tests" + int currentTokenIndex = startTokenIndex + 1; + String arg_from; + String arg_in; + while (currentTokenIndex < endTokenIndex) { + ReadableString key = expression_getToken(tokens, currentTokenIndex, U""); + ReadableString value = expression_getToken(tokens, currentTokenIndex + 1, U""); + if (string_caseInsensitiveMatch(key, U"from")) { + if (string_length(value) == 0) { + throwError(U"Missing folder path after 'from' keyword in 'projects' command!\n"); + } else { + printText(U"Using ", value, U" as the 'from' argument.\n"); + arg_from = string_unmangleQuote(value); + // Consume both key and value. + currentTokenIndex += 2; + } + } else if (string_caseInsensitiveMatch(key, U"in")) { + if (string_length(value) == 0) { + throwError(U"Missing file name pattern after 'in' keyword in 'projects' command!\n"); + } else { + printText(U"Using ", value, U" as the 'in' argument.\n"); + arg_in = string_unmangleQuote(value); + // Consume both key and value. + currentTokenIndex += 2; + } + } else { + throwError(U"Unexpected key '", key, "' in 'projects' command!\n"); + } + } + if (string_length(arg_from) == 0 && string_length(arg_in) == 0) { + throwError(U"Need 'from' and 'in' keywords in 'projects' command!\n"); + } else if (string_length(arg_from) == 0) { + throwError(U"Missing 'from' keyword in 'projects' command!\n"); + } else if (string_length(arg_in) == 0) { + throwError(U"Missing 'in' keywords in 'projects' command!\n"); + } else { + findFilesAsProjects(target, file_combinePaths(fromPath, arg_in), arg_from); + } } 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. @@ -235,7 +355,6 @@ static void interpretLine(Machine &target, const List &tokens, int64_t s validateSettings(target, U"in target after explicitly assigning a value to a variable\n"); } else { String errorMessage = U"Failed to parse statement: "; - printText(U"Failed to parse statement of tokens: "); for (int64_t t = startTokenIndex; t <= endTokenIndex; t++) { string_append(errorMessage, U" ", string_mangleQuote(tokens[t])); } diff --git a/Source/tools/builder/code/Machine.h b/Source/tools/builder/code/Machine.h index f492d2b7..122cd919 100644 --- a/Source/tools/builder/code/Machine.h +++ b/Source/tools/builder/code/Machine.h @@ -22,6 +22,8 @@ void assignValue(Machine &target, const dsr::ReadableString &key, const dsr::Rea void evaluateScript(Machine &target, const ReadableString &scriptPath); void inheritMachine(Machine &child, const Machine &parent); +void cloneMachine(Machine &child, const Machine &parent); + void argumentsToSettings(Machine &settings, const List &arguments, int64_t firstArgument, int64_t lastArgument); void printSettings(const Machine &settings); diff --git a/Source/tools/builder/code/analyzer.cpp b/Source/tools/builder/code/analyzer.cpp index dadeeed7..2b1d99e9 100644 --- a/Source/tools/builder/code/analyzer.cpp +++ b/Source/tools/builder/code/analyzer.cpp @@ -1,4 +1,5 @@  +#include "analyzer.h" #include "generator.h" #include "Machine.h" @@ -404,17 +405,11 @@ static void crawlSource(ProjectContext &context, ReadableString absolutePath) { } } -void build(SessionContext &output, ReadableString projectPath, Machine &settings); - static List initializedProjects; -// Using a project file path and input arguments. -void buildProject(SessionContext &output, ReadableString projectFilePath, Machine &sharedsettings) { - Machine settings(file_getPathlessName(projectFilePath)); - inheritMachine(settings, sharedsettings); - validateSettings(settings, string_combine(U"in settings after inheriting settings from caller, for ", projectFilePath, U"\n")); - printText(U"Building project at ", projectFilePath, U"\n"); +static void buildProjectFromSettings(SessionContext &output, const ReadableString &path, Machine &settings) { + printText(U"Building project at ", path, U"\n"); // Check if this project has begun building previously during this session. - String absolutePath = file_getAbsolutePath(projectFilePath); + String absolutePath = file_getAbsolutePath(path); for (int64_t 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"); @@ -423,15 +418,11 @@ void buildProject(SessionContext &output, ReadableString projectFilePath, Machin } // 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(settings, projectFilePath); - validateSettings(settings, string_combine(U"in settings after evaluateScript in buildProject, for ", projectFilePath, U"\n")); // Find out where things are located. - String projectPath = file_getAbsoluteParentFolder(projectFilePath); + String projectPath = file_getAbsoluteParentFolder(path); // Get the project's name. - String projectName = file_getPathlessName(file_getExtensionless(projectFilePath)); + String projectName = file_getPathlessName(file_getExtensionless(path)); // If no application path is given, the new executable will be named after the project and placed in the same folder. String fullProgramPath = getFlag(settings, U"ProgramPath", projectName); if (string_length(output.executableExtension) > 0) { @@ -439,47 +430,69 @@ void buildProject(SessionContext &output, ReadableString projectFilePath, Machin } // Interpret ProgramPath relative to the project path. fullProgramPath = file_getTheoreticalAbsolutePath(fullProgramPath, projectPath); - // Build other projects. + + // Build projects from files. (used for running many tests) + for (int64_t b = 0; b < settings.projectFromSourceFilenames.length(); b++) { + buildFromFile(output, settings.projectFromSourceFilenames[b], settings.projectFromSourceSettings[b]); + } + + // Build other projects. (used for compiling programs that the main program should call) for (int64_t b = 0; b < settings.otherProjectPaths.length(); b++) { - build(output, settings.otherProjectPaths[b], settings.otherProjectSettings[b]); + buildFromFolder(output, settings.otherProjectPaths[b], settings.otherProjectSettings[b]); } - validateSettings(settings, string_combine(U"in settings after building other projects in buildProject, for ", projectFilePath, U"\n")); + validateSettings(settings, string_combine(U"in settings after building other projects in buildProject, for ", path, U"\n")); // 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(fullProgramPath) == 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 and ", fullProgramPath, U" was found.\n"); + printText(U"Skipping build of ", path, U" because the SkipIfBinaryExists flag was given and ", fullProgramPath, U" was found.\n"); return; } // Once we know where the binary is and that it should be built, we can start searching for source code. for (int64_t o = 0; o < settings.crawlOrigins.length(); o++) { crawlSource(context, settings.crawlOrigins[o]); } - validateSettings(settings, string_combine(U"in settings after crawling source in buildProject, for ", projectFilePath, U"\n")); + validateSettings(settings, string_combine(U"in settings after crawling source in buildProject, for ", path, U"\n")); // 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); } gatherBuildInstructions(output, context, settings, fullProgramPath); - validateSettings(settings, string_combine(U"in settings after gathering build instructions in buildProject, for ", projectFilePath, U"\n")); + validateSettings(settings, string_combine(U"in settings after gathering build instructions in buildProject, for ", path, U"\n")); +} + +// Using a project file path and input arguments. +void buildProject(SessionContext &output, ReadableString projectFilePath, Machine &sharedSettings) { + // Inherit external settings. + Machine settings(file_getPathlessName(projectFilePath)); + inheritMachine(settings, sharedSettings); + validateSettings(settings, string_combine(U"in settings after inheriting settings from caller, for ", projectFilePath, U"\n")); + + // Evaluate the project's script. + printText(U"Executing project file from ", projectFilePath, U".\n"); + evaluateScript(settings, projectFilePath); + validateSettings(settings, string_combine(U"in settings after evaluateScript in buildProject, for ", projectFilePath, U"\n")); + + // Complete the project. + buildProjectFromSettings(output, projectFilePath, settings); } // Using a folder path and input arguments for all projects. -void buildProjects(SessionContext &output, ReadableString projectFolderPath, Machine &sharedsettings) { +void buildProjects(SessionContext &output, ReadableString projectFolderPath, Machine &sharedSettings) { printText(U"Building all projects in ", projectFolderPath, U"\n"); - file_getFolderContent(projectFolderPath, [&sharedsettings, &output](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) { + file_getFolderContent(projectFolderPath, [&sharedSettings, &output](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) { if (entryType == EntryType::Folder) { - buildProjects(output, entryPath, sharedsettings); + buildProjects(output, entryPath, sharedSettings); } else if (entryType == EntryType::File) { - ReadableString extension = string_upperCase(file_getExtension(entryName)); - if (string_match(extension, U"DSRPROJ")) { - buildProject(output, entryPath, sharedsettings); + ReadableString extension = file_getExtension(entryName); + if (string_caseInsensitiveMatch(extension, U"DSRPROJ")) { + buildProject(output, entryPath, sharedSettings); } } }); } -void build(SessionContext &output, ReadableString projectPath, Machine &sharedsettings) { +void buildFromFolder(SessionContext &output, ReadableString projectPath, Machine &sharedSettings) { EntryType entryType = file_getEntryType(projectPath); printText(U"Building anything at ", projectPath, U" which is ", entryType, U"\n"); if (entryType == EntryType::File) { @@ -488,9 +501,74 @@ void build(SessionContext &output, ReadableString projectPath, Machine &sharedse 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, sharedsettings); + buildProject(output, projectPath, sharedSettings); } } else if (entryType == EntryType::Folder) { - buildProjects(output, projectPath, sharedsettings); + buildProjects(output, projectPath, sharedSettings); + } +} + +// TODO: Create a reusable function for building a project from settings and call it from both buildProject and buildFromFile. +void buildFromFile(SessionContext &output, ReadableString mainPath, Machine &sharedSettings) { + // Inherit settings, flags and dependencies from the parent, because they do not exist in single source files. + Machine settings(file_getPathlessName(mainPath)); + cloneMachine(settings, sharedSettings); + + ReadableString extension = file_getExtension(mainPath); + if (!(string_caseInsensitiveMatch(extension, U"c") || string_caseInsensitiveMatch(extension, U"cpp"))) { + throwError(U"Creating projects from source files is currently only supported for *.c and *.cpp, but the extension was '", extension, U"'."); + } + + + settings.crawlOrigins.push(mainPath); + + validateSettings(settings, string_combine(U"in settings after inheriting settings from caller, for ", mainPath, U"\n")); + + buildProjectFromSettings(output, mainPath, settings); + /* + printText(U"Building project from ", mainPath, U"\n"); + printSettings(sharedSettings); + ReadableString extension = file_getExtension(mainPath); + if (!(string_caseInsensitiveMatch(extension, U"c") || string_caseInsensitiveMatch(extension, U"cpp"))) { + throwError(U"Creating projects from source files is currently only supported for *.c and *.cpp, but the extension was '", extension, U"'."); + } + Machine settings(file_getPathlessName(file_getExtensionless(mainPath))); + inheritMachine(settings, sharedSettings, false); + validateSettings(settings, string_combine(U"in settings after inheriting settings from caller, for ", mainPath, U"\n")); + // Check if this project has begun building previously during this session. + String absolutePath = file_getAbsolutePath(mainPath); + for (int64_t p = 0; p < initializedProjects.length(); p++) { + if (string_caseInsensitiveMatch(absolutePath, initializedProjects[p])) { + throwError(U"Found duplicate requests to build from the same main file ", 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. + ProjectContext context; + // Find out where things are located. + String projectPath = file_getAbsoluteParentFolder(mainPath); + // Get the project's name. + String projectName = file_getPathlessName(file_getExtensionless(mainPath)); + // If no application path is given, the new executable will be named after the project and placed in the same folder. + String fullProgramPath = getFlag(settings, U"ProgramPath", projectName); + if (string_length(output.executableExtension) > 0) { + string_append(fullProgramPath, output.executableExtension); } + // Interpret ProgramPath relative to the project path. + fullProgramPath = file_getTheoreticalAbsolutePath(fullProgramPath, projectPath); + // Search for source code from the given file. + crawlSource(context, mainPath); + // Search for source code from the inherited project's dependencies. + for (int64_t o = 0; o < settings.crawlOrigins.length(); o++) { + crawlSource(context, settings.crawlOrigins[o]); + } + validateSettings(settings, string_combine(U"in settings after crawling source from ", mainPath, U"\n")); + // Once we are done finding all source files, we can resolve the dependencies to create a graph connected by indices. + resolveDependencies(context); + gatherBuildInstructions(output, context, settings, fullProgramPath); + printSettings(settings); + validateSettings(settings, string_combine(U"in settings after gathering build instructions for a project created automatically for ", mainPath, U"\n")); + */ } diff --git a/Source/tools/builder/code/analyzer.h b/Source/tools/builder/code/analyzer.h index 7d2a820a..715028bc 100644 --- a/Source/tools/builder/code/analyzer.h +++ b/Source/tools/builder/code/analyzer.h @@ -16,14 +16,17 @@ void resolveDependencies(ProjectContext &context); void printDependencies(ProjectContext &context); // Build anything in projectPath. -void build(SessionContext &output, ReadableString projectPath, Machine &sharedsettings); +void buildFromFolder(SessionContext &output, ReadableString projectPath, Machine &sharedSettings); + +// Create a project from crawling a single source file and build it. +void buildFromFile(SessionContext &output, ReadableString mainPath, Machine &sharedSettings); // Build the project in projectFilePath. // Settings must be taken by value to prevent side-effects from spilling over between different scripts. -void buildProject(SessionContext &output, ReadableString projectFilePath, Machine &sharedsettings); +void buildProject(SessionContext &output, ReadableString projectFilePath, Machine &sharedSettings); // Build all projects in projectFolderPath. -void buildProjects(SessionContext &output, ReadableString projectFolderPath, Machine &sharedsettings); +void buildProjects(SessionContext &output, ReadableString projectFolderPath, Machine &sharedSettings); void gatherBuildInstructions(SessionContext &output, ProjectContext &context, Machine &settings, ReadableString programPath); diff --git a/Source/tools/builder/code/builderTypes.h b/Source/tools/builder/code/builderTypes.h index 0fbc2843..795ef262 100644 --- a/Source/tools/builder/code/builderTypes.h +++ b/Source/tools/builder/code/builderTypes.h @@ -16,13 +16,23 @@ struct Flag { }; struct Machine { + // Name of this project. String projectName; + // Variables that can be assigned and used for logic. List variables; + // The flags to give the compiler. List compilerFlags; + // The flags to give the linker. List linkerFlags; + // A list of implementation files to start crawling from, usually main.cpp or a disconnected backend implementation. List crawlOrigins; - List otherProjectPaths; // Corresponding to otherProjectSettings. - List otherProjectSettings; // Corresponding to otherProjectPaths. + // Paths to look for other projects in. + List otherProjectPaths; + List otherProjectSettings; + // Filenames to create projects for automatically without needing project files for each. + // Useful for running automated tests, so that memory leaks can easily be narrowed down to the test causing the leak. + List projectFromSourceFilenames; + List projectFromSourceSettings; // When activeStackDepth < currentStackDepth, we are skipping false cases. int64_t currentStackDepth = 0; // How many scopes we are inside of, from the root script including all the others. int64_t activeStackDepth = 0; diff --git a/Source/tools/builder/code/main.cpp b/Source/tools/builder/code/main.cpp index 761ff0c4..ca5ff4a0 100644 --- a/Source/tools/builder/code/main.cpp +++ b/Source/tools/builder/code/main.cpp @@ -41,21 +41,37 @@ Project files: * x is assigned a boolean value telling if the content of a matches "abc". (case sensitive comparison) x = a matches "abc" 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" - * Add a linker flag as is for direct control - LinkerFlag -lLibrary - * Add a linker flag with automatic prefix for future proofing - Link Library - * Add a compiler flag as is - CompilerFlag -DMACRO + Finding source code for the current project: + * Add file.cpp and other implementations found through includes into the list of source code to compile and link. + Crawl "folder/file.cpp" + Settings for compiling: + * Add a compiler flag as is + CompilerFlag -DMACRO + Settings for linking: + * Add a linker flag as is for direct control + LinkerFlag -lLibrary + * Add a linker flag with automatic prefix for future proofing + Link Library + Building other projects at the same time: + * Build all projects in myFolder with the SkipIfBinaryExists flag in arbitrary order before continuing with compilation + Build "../myFolder" SkipIfBinaryExists + Building a project crawling from each file matching a pattern: + All variables are inherited, so no variables are given to the command. + * Build a project for each file ending with 'Test.cpp' in a folder named 'tests'. + Projects from "*Test.cpp" in "tests" + * Build a project for each file starting with 'main_' and ending with '.cpp' in a folder named "code/projects". + Projects from "main_*.cpp" in "code/projects" + * Build a project for each file named 'main.cpp' in a folder named "examples". + Projects from "main_*.cpp" in "examples" + * You can also swap argument order like this, because it is designed to be easily extended with more keywords if needed. + Projects in "tests" from "*Test.cpp" Systems: * Linux Set to non-zero on Linux or similar operating systems. * Windows Set to non-zero on MS-Windows. + * MacOS + Set to non-zero on MacOS. 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. @@ -160,7 +176,7 @@ void dsrMain(List args) { executableExtension = U".exe"; } SessionContext buildContext = SessionContext(tempFolder, executableExtension); - build(buildContext, projectPath, settings); + buildFromFolder(buildContext, projectPath, settings); validateSettings(settings, U"in settings after executing the root build script (in main)"); if (language == ScriptLanguage::Unknown) { // Call the compiler directly. diff --git a/Source/tools/builder/compilerPaths_posix.txt b/Source/tools/builder/compilerPaths_posix.txt new file mode 100644 index 00000000..cfc129cc --- /dev/null +++ b/Source/tools/builder/compilerPaths_posix.txt @@ -0,0 +1,6 @@ +/usr/bin/g++ +/usr/bin/clang +/opt/homebrew/bin/g++ +/opt/homebrew/bin/clang + +Empty lines terminate the search, so keep the paths without any gaps in between. diff --git a/Source/tools/builder/compilerPaths_windows.txt b/Source/tools/builder/compilerPaths_windows.txt new file mode 100644 index 00000000..ffab5e0b --- /dev/null +++ b/Source/tools/builder/compilerPaths_windows.txt @@ -0,0 +1,44 @@ +%Cygwin64%\bin\g++.exe +%ProgramFiles%\Cygwin64\bin\g++.exe +%ProgramFiles%\mingw-w64\bin\g++.exe +%ProgramFiles%\TDM-GCC-64\bin\g++.exe +%ProgramFiles%\mingw-w64\x86_64\mingw32\bin\g++.exe +C:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +D:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +E:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +F:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +G:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +H:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +I:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +J:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +K:\Program\CodeBlocks\MinGW\bin\x86_64-w64-mingw32-g++.exe +%ProgramFiles%\LLVM\bin\clang.exe +%ProgramFiles%\LLVM\bin\clang++.exe +%ProgramFiles(x86)%\LLVM\bin\clang.exe +%ProgramFiles(x86)%\LLVM\bin\clang++.exe +%LocalDisk%\LLVM\bin\clang.exe +%LocalDisk%\LLVM\bin\clang++.exe +%LocalDisk%\PortableApps\LLVM\bin\clang.exe +%LocalDisk%\PortableApps\LLVM\bin\clang++.exe +%LocalDisk%\mingw-w64\bin\clang.exe +%LocalDisk%\mingw-w64\bin\clang++.exe +%Cygwin%\bin\g++.exe +%ProgramFiles%\LLVM\bin\g++.exe +%ProgramFiles%\MinGW\bin\g++.exe +%ProgramFiles%\Cygwin\bin\g++.exe +%LocalDisk%\Tools\MinGW\bin\g++.exe +%ProgramFiles%\GnuWin32\bin\g++.exe +%LocalDisk%\Development\GCC\bin\g++.exe +%ProgramFiles(x86)%\GnuWin32\bin\g++.exe +%ProgramFiles%\CodeBlocks\MinGW\bin\g++.exe +%ProgramFiles(x86)%\CodeBlocks\MinGW\bin\g++.exe +%LocalDisk%\GnuWin32\bin\clang.exe +%LocalDisk%\GnuWin32\bin\clang++.exe +%LocalDisk%\TDM-GCC\bin\clang.exe +%LocalDisk%\TDM-GCC\bin\clang++.exe +%LocalDisk%\Cygwin\bin\clang.exe +%LocalDisk%\Cygwin\bin\clang++.exe +%LocalDisk%\Cygwin64\bin\clang.exe +%LocalDisk%\Cygwin64\bin\clang++.exe + +Empty lines terminate the search, so keep the paths without any gaps in between. diff --git a/Source/tools/wizard/main.cpp b/Source/tools/wizard/main.cpp index 47d81533..fbdeb369 100644 --- a/Source/tools/wizard/main.cpp +++ b/Source/tools/wizard/main.cpp @@ -43,7 +43,6 @@ Project::Project(const ReadableString &projectFilePath) String projectFolderPath = file_getRelativeParentFolder(projectFilePath); String extensionlessProjectPath = file_getExtensionless(projectFilePath); this->title = file_getPathlessName(extensionlessProjectPath); - // TODO: Get the native extension for each type of file? .exe, .dll, .so... #ifdef USE_MICROSOFT_WINDOWS this->executableFilePath = string_combine(extensionlessProjectPath, U".exe"); #else