From bf42a3fe559b53b62db9c6363efbec612804dbe7 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 21 Sep 2018 10:22:24 +0200 Subject: Add support for running zig tests --- backend/BackendUtils.cpp | 27 ++++++-- backend/BackendUtils.hpp | 2 + backend/ninja/Ninja.cpp | 159 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 165 insertions(+), 23 deletions(-) (limited to 'backend') diff --git a/backend/BackendUtils.cpp b/backend/BackendUtils.cpp index ac71b07..3c9dd71 100644 --- a/backend/BackendUtils.cpp +++ b/backend/BackendUtils.cpp @@ -27,28 +27,43 @@ namespace backend TINYDIR_STRING("c++") }; - sibs::Language BackendUtils::getFileLanguage(tinydir_file *file) + sibs::FileString BackendUtils::getFileExtension(const sibs::FileString &filepath) { - if(!file->is_reg) - return sibs::Language::NONE; + size_t indexOfDot = filepath.find_last_of('.'); + if(indexOfDot == sibs::FileString::npos) + return TINYDIR_STRING(""); + + indexOfDot += 1; + return filepath.substr(indexOfDot); + } + sibs::Language BackendUtils::getFileLanguage(const _tinydir_char_t *extension) + { for(const _tinydir_char_t *sourceFileExtension : cFileExtensions) { - if(_tinydir_strcmp(sourceFileExtension, file->extension) == 0) + if(_tinydir_strcmp(sourceFileExtension, extension) == 0) return sibs::Language::C; } for(const _tinydir_char_t *sourceFileExtension : cppFileExtensions) { - if(_tinydir_strcmp(sourceFileExtension, file->extension) == 0) + if(_tinydir_strcmp(sourceFileExtension, extension) == 0) return sibs::Language::CPP; } - if(_tinydir_strcmp(TINYDIR_STRING("zig"), file->extension) == 0) + if(_tinydir_strcmp(TINYDIR_STRING("zig"), extension) == 0) return sibs::Language::ZIG; return sibs::Language::NONE; } + + sibs::Language BackendUtils::getFileLanguage(tinydir_file *file) + { + if(!file->is_reg) + return sibs::Language::NONE; + + return getFileLanguage(file->extension); + } void BackendUtils::collectSourceFiles(const _tinydir_char_t *projectPath, Ninja *ninjaProject, const SibsConfig &sibsConfig, bool recursive) { diff --git a/backend/BackendUtils.hpp b/backend/BackendUtils.hpp index acef0ca..f99d7e5 100644 --- a/backend/BackendUtils.hpp +++ b/backend/BackendUtils.hpp @@ -9,6 +9,8 @@ namespace backend class BackendUtils { public: + static sibs::FileString getFileExtension(const sibs::FileString &filepath); + static sibs::Language getFileLanguage(const _tinydir_char_t *extension); static sibs::Language getFileLanguage(tinydir_file *file); static void collectSourceFiles(const _tinydir_char_t *projectPath, Ninja *ninjaProject, const sibs::SibsConfig &sibsConfig, bool recursive = true); }; diff --git a/backend/ninja/Ninja.cpp b/backend/ninja/Ninja.cpp index ca88f40..c642a4c 100644 --- a/backend/ninja/Ninja.cpp +++ b/backend/ninja/Ninja.cpp @@ -474,6 +474,7 @@ namespace backend case Compiler::MSVC: { includeStartStr = "/I"; + break; } default: assert(false); @@ -527,6 +528,72 @@ namespace backend return result; } + static vector extractLibraryPathsWithoutFlags(Compiler compiler, const string &cLibraries) + { + vector result; + + size_t i = 0; + while(i < cLibraries.size()) + { + char c = cLibraries[i]; + if(c == '\'' || c == '"') + { + // TODO: Handle quote escaping + char quoteSymbol = c; + ++i; + size_t libraryIndexStart = i; + while(i < cLibraries.size() && cLibraries[i] != quoteSymbol) + { + ++i; + } + size_t libraryIndexEnd = i; + ++i; + + if(strncmp(&cLibraries[libraryIndexStart], "-l", 2) == 0) + libraryIndexStart += 2; + else if(strcmp(&cLibraries[libraryIndexStart], "-pthread") == 0) + continue; + + size_t libraryPathLength = libraryIndexEnd - libraryIndexStart; + if(libraryPathLength > 0) + result.push_back(ninja::NinjaArg::createRaw(cLibraries.substr(libraryIndexStart, libraryPathLength))); + } + else if(c != ' ') + { + // TODO: Handle space escaping + size_t libraryIndexStart = i; + while(i < cLibraries.size() && cLibraries[i] != ' ') + { + ++i; + } + size_t libraryIndexEnd = i; + ++i; + + if(strncmp(&cLibraries[libraryIndexStart], "-l", 2) == 0) + libraryIndexStart += 2; + else if(strcmp(&cLibraries[libraryIndexStart], "-pthread") == 0) + continue; + + size_t libraryPathLength = libraryIndexEnd - libraryIndexStart; + if(libraryPathLength > 0) + result.push_back(ninja::NinjaArg::createRaw(cLibraries.substr(libraryIndexStart, libraryPathLength))); + } + } + + return result; + } + + static vector convertCLibrariesToZigLibraries(Compiler compiler, const string &cLibraries) + { + vector result; + result = extractLibraryPathsWithoutFlags(compiler, cLibraries); + for(ninja::NinjaArg &include : result) + { + include.arg = "--library \"" + include.arg + "\""; + } + return result; + } + Result Ninja::build(const SibsConfig &config, const _tinydir_char_t *savePath, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallback, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { if (!sourceFiles.empty()) @@ -809,18 +876,29 @@ namespace backend // TODO: Specify -mconsole or -mwindows for windows. // TODO: Convert sibs defines to const variables in a zig file that other zig files can include (like a config file). + // TODO: Remove --library c if project does not depend on c libraries or project only has .zig files + vector zigTestArgs = { + ninja::NinjaArg::createRaw("zig test $in --output $out -isystem ../../ --color on --library c $globalIncDirZig") + }; + // TODO: Find a way to do this more efficiently vector cflagsIncludes = convertCFlagsIncludesToZigIncludes(config.getCompiler(), cflags); + vector zigLibraryFlags = convertCLibrariesToZigLibraries(config.getCompiler(), allLinkerFlags); + vector compileZigArgs = { ninja::NinjaArg::createRaw("zig build-obj"), ninja::NinjaArg::createRaw("$in"), ninja::NinjaArg::createRaw("--output $out"), - ninja::NinjaArg::createRaw("-isystem ."), + ninja::NinjaArg::createRaw("-isystem ../../"), ninja::NinjaArg::createRaw("--color on"), ninja::NinjaArg::createRaw("--library c"), // TODO: Remove this if project does not depend on c libraries or project only has .zig files ninja::NinjaArg::createRaw("$globalIncDirZig") }; + // TODO: Verify if we really need to add all libraries for every object file compileZigArgs.insert(compileZigArgs.end(), cflagsIncludes.begin(), cflagsIncludes.end()); + compileZigArgs.insert(compileZigArgs.end(), zigLibraryFlags.begin(), zigLibraryFlags.end()); + zigTestArgs.insert(zigTestArgs.end(), cflagsIncludes.begin(), cflagsIncludes.end()); + zigTestArgs.insert(zigTestArgs.end(), zigLibraryFlags.begin(), zigLibraryFlags.end()); if(config.getOptimizationLevel() == sibs::OPT_LEV_RELEASE) { @@ -829,6 +907,11 @@ namespace backend ninja::NinjaArg::createRaw("--release-safe"), ninja::NinjaArg::createRaw("--strip") }); + + zigTestArgs.insert(zigTestArgs.end(), { + ninja::NinjaArg::createRaw("--release-safe"), + ninja::NinjaArg::createRaw("--strip") + }); } if(libraryType == LibraryType::STATIC) @@ -837,9 +920,11 @@ namespace backend compileZigArgs.push_back(ninja::NinjaArg::createRaw("-rdynamic")); ninja::NinjaRule *compileZigRule = ninjaBuildFile.createRule("compile_zig", compileZigArgs); + ninja::NinjaRule *testZigRule = ninjaBuildFile.createRule("zig_test", zigTestArgs); bool usesCFiles = false; bool usesCppFiles = false; + bool zigTest = (config.zigTestAllFiles || !config.zigTestFiles.empty()); vector objectNames; objectNames.reserve(sourceFiles.size()); @@ -868,7 +953,10 @@ namespace backend { objectName += "zig-cache/" + sourceFile.filepath; objectName += getObjectFileExtension(config.getCompiler()); - ninjaBuildFile.build(compileZigRule, "../../" + sourceFile.filepath, objectName, {}); + if(zigTest) + ninjaBuildFile.build(testZigRule, "../../" + sourceFile.filepath, objectName, {}); + else + ninjaBuildFile.build(compileZigRule, "../../" + sourceFile.filepath, objectName, {}); break; } default: @@ -882,7 +970,7 @@ namespace backend // they should be built with zig if project only contains zig files. // But how to combine object files with zig? build-exe only wants to accept .zig files string projectGeneratedBinaryFlags; - if (!sourceFiles.empty()) + if (!sourceFiles.empty() && !zigTest) { string projectGeneratedBinary = "\""; projectGeneratedBinary += savePathUtf8; @@ -1070,6 +1158,10 @@ namespace backend } projectGeneratedBinaryFlags = allLinkerFlags + " " + projectGeneratedBinary; + } + + if(!sourceFiles.empty()) + { string result = ninjaBuildFile.generate(); Result fileOverwriteResult = sibs::fileOverwrite(ninjaBuildFilePath.c_str(), sibs::StringView(result.data(), result.size())); if (fileOverwriteResult.isErr()) @@ -1089,9 +1181,6 @@ namespace backend } } - // TODO: If tests are being run (sibs test) and root project is an executable, do not run compile (above code) as executable. - // Sibs test will compile root project as dynamic library so you end up compiling the project twice, first as an executable and then as a dynamic library. - // Even if the root project has been built before and there is cached object, it will take a few seconds to run compile Result buildTestResult = buildTests(projectGeneratedBinaryFlags, config, savePath, dependencyExportIncludeDirs); if(!buildTestResult) return buildTestResult; @@ -1132,6 +1221,8 @@ namespace backend FileType projectConfFileType = getFileType(projectConfFilePath.c_str()); SibsTestConfig sibsTestConfig(config.getCompiler(), testSourceDirNative, config.getOptimizationLevel()); sibsTestConfig.setSanitize(config.getSanitize()); + sibsTestConfig.zigTestFiles = move(config.zigTestFiles); + sibsTestConfig.zigTestAllFiles = config.zigTestAllFiles; if(projectConfFileType == FileType::REGULAR) { Result result = Config::readFromFile(projectConfFilePath.c_str(), sibsTestConfig); @@ -1144,7 +1235,38 @@ namespace backend if(!projectGeneratedBinaryFlags.empty()) ninja.addDependency(projectGeneratedBinaryFlags); - backend::BackendUtils::collectSourceFiles(testSourceDirNative.c_str(), &ninja, sibsTestConfig, false); + bool zigTest = false; + if(config.zigTestAllFiles) + { + backend::BackendUtils::collectSourceFiles(testSourceDirNative.c_str(), &ninja, sibsTestConfig, false); + // TODO: This can be optimized as well. No need to insert non-zig files if we are going to remove them. + // Maybe pass a filter callback function to @collectSourceFiles. + for(auto it = ninja.sourceFiles.begin(); it != ninja.sourceFiles.end(); ) + { + if(it->language != sibs::Language::ZIG) + { + it = ninja.sourceFiles.erase(it); + } + else + { + ++it; + } + } + zigTest = true; + } + else if(!config.zigTestFiles.empty()) + { + ninja.sourceFiles.reserve(config.zigTestFiles.size()); + for(const sibs::FileString &testFile : config.zigTestFiles) + { + ninja.addSourceFile(sibs::Language::ZIG, testFile.c_str()); + } + zigTest = true; + } + else + { + backend::BackendUtils::collectSourceFiles(testSourceDirNative.c_str(), &ninja, sibsTestConfig, false); + } if(!ninja.getSourceFiles().empty()) { @@ -1170,16 +1292,19 @@ namespace backend if(!buildFileResult) return buildFileResult; } - - FileString testExecutableName = buildPath; - testExecutableName += TINYDIR_STRING("/"); - testExecutableName += toFileString(sibsTestConfig.getPackageName()); - Result runTestResult = exec(testExecutableName.c_str(), true); - if(!runTestResult) - return Result::Err(runTestResult); - - if(runTestResult.unwrap().exitCode != 0) - return Result::Err("Tests failed", runTestResult.unwrap().exitCode); + + if(!zigTest) + { + FileString testExecutableName = buildPath; + testExecutableName += TINYDIR_STRING("/"); + testExecutableName += toFileString(sibsTestConfig.getPackageName()); + Result runTestResult = exec(testExecutableName.c_str(), true); + if(!runTestResult) + return Result::Err(runTestResult); + + if(runTestResult.unwrap().exitCode != 0) + return Result::Err("Tests failed", runTestResult.unwrap().exitCode); + } } } -- cgit v1.2.3