#include #include "../BackendUtils.hpp" #include "Ninja.hpp" #include "../../include/FileUtil.hpp" #include "../../include/Exec.hpp" #include "../../include/PkgConfig.hpp" #include "../../include/GlobalLib.hpp" #include #include using namespace std; using namespace sibs; #if OS_FAMILY == OS_FAMILY_POSIX #define nprintf printf #else #define nprintf wprintf #endif namespace backend { static std::string escape(const std::string &str) { std::string result; result.reserve(str.size()); for(char c : str) { if(c == '\n') result += "$\n"; else if(c == '$') result += "$$"; else if(c == ' ') result += "$ "; else if(c == ':') result += "$:"; else result += c; } return result; } static string join(const vector &list, const char *joinStr) { if(list.empty()) return ""; string result; long stringSize = 0; long joinStrLen = strlen(joinStr); int i = 0; for(const string &str : list) { stringSize += str.size(); if(!str.empty() && i > 0) stringSize += joinStrLen; ++i; } result.reserve(stringSize); i = 0; for(const string &str : list) { if(i > 0) result += joinStr; result += str; ++i; } return result; } static bool endsWith(const string &str, const string &endWithStr) { if(endWithStr.size() > str.size()) return false; else return strncmp(&str[str.size() - endWithStr.size()], &endWithStr[0], endWithStr.size()) == 0; } static Ninja::LibraryType getNinjaLibraryType(PackageType packageType) { switch(packageType) { case PackageType::EXECUTABLE: return Ninja::LibraryType::EXECUTABLE; case PackageType::STATIC: return Ninja::LibraryType::STATIC; case PackageType::DYNAMIC: case PackageType::LIBRARY: return Ninja::LibraryType::DYNAMIC; default: assert(false); return (Ninja::LibraryType)-1; } } static string getIncludeOptionFlag(Compiler compiler, const string &filepath) { string result; switch (compiler) { case Compiler::MINGW_W64: case Compiler::GCC: { result = "'-I"; result += filepath; result += "'"; break; } case Compiler::MSVC: { result = "/I \""; result += filepath; result += "\""; break; } default: assert(false); break; } return result; } static string getDefineFlag(Compiler compiler, const string &name, const string &value) { switch (compiler) { case Compiler::MINGW_W64: case Compiler::GCC: { if(value.empty()) return "'-D" + name + "=" + value + "'"; else return "'-D" + name + "'"; } case Compiler::MSVC: { if(value.empty()) return "\"/D" + name + "=" + value + "\""; else return "\"/D" + name + "\""; } default: assert(false); break; } return ""; } static string getObjectFileNameFlag(Compiler compiler, const string &objectFileName) { string result; switch (compiler) { case Compiler::MINGW_W64: case Compiler::GCC: { result = "-o "; result += objectFileName; break; } case Compiler::MSVC: { result = "/Fo"; result += objectFileName; break; } default: assert(false); break; } return result; } static vector getLanguageVersionFlag(Compiler compiler, CVersion cVersion) { switch (compiler) { case Compiler::MINGW_W64: case Compiler::GCC: { switch(cVersion) { case CVersion::C89: return { ninja::NinjaArg("-std=c89"), ninja::NinjaArg("-pedantic") }; case CVersion::C99: return { ninja::NinjaArg("-std=c99"), ninja::NinjaArg("-pedantic") }; case CVersion::C11: return { ninja::NinjaArg("-std=c11"), ninja::NinjaArg("-pedantic") }; } break; } case Compiler::MSVC: { // It's not possible to specify C version in MSVC, programmer will have to instead be careful to not use features that are outside // the desired version features... // MSVC specific extensions can be disabled by /Za /Ze but we dont want to do that, code could have check if compiler is MSVC // before using msvc extensions return {}; } } assert(false); return {}; } static vector getLanguageVersionFlag(Compiler compiler, CPPVersion cppVersion) { switch (compiler) { case Compiler::MINGW_W64: case Compiler::GCC: { switch(cppVersion) { case CPPVersion::CPP11: return { ninja::NinjaArg("-std=c++11"), ninja::NinjaArg("-pedantic") }; case CPPVersion::CPP14: return { ninja::NinjaArg("-std=c++14"), ninja::NinjaArg("-pedantic") }; case CPPVersion::CPP17: return { ninja::NinjaArg("-std=c++17"), ninja::NinjaArg("-pedantic") }; } break; } case Compiler::MSVC: { switch(cppVersion) { // Use /Za flag? case CPPVersion::CPP11: return { ninja::NinjaArg("/std:c++11") }; case CPPVersion::CPP14: return { ninja::NinjaArg("/std:c++14") }; case CPPVersion::CPP17: return { ninja::NinjaArg("/std:c++17") }; } break; } } assert(false); return {}; } static const char* getObjectFileExtension(Compiler compiler) { switch (compiler) { case Compiler::MINGW_W64: case Compiler::GCC: return ".o"; case Compiler::MSVC: return ".obj"; default: return nullptr; } } Ninja::Ninja() { } void Ninja::addGlobalIncludeDirs(const string &globalIncludeDirs) { customGlobalIncludeDirs = globalIncludeDirs; } void Ninja::addSourceFile(sibs::Language language, const char *filepath) { string filePathStr = filepath ? filepath : ""; if(filepath && !containsSourceFile(filePathStr)) sourceFiles.push_back({ language, filePathStr }); } void Ninja::setTestSourceDir(const char *dir) { string dirStr = dir ? dir : ""; if(dir) testSourceDir = dirStr; } void Ninja::addDependency(const std::string &binaryFile) { if(!containsDependency(binaryFile)) binaryDependencies.emplace_back(binaryFile); } void Ninja::addDynamicDependency(const std::string &dependency) { if(!containsDynamicDependency(dependency)) dynamicDependencies.push_back(dependency); } void Ninja::addSubProject(Ninja *subProject, SibsConfig *config, sibs::FileString &&buildPath) { subProjects.emplace_back(NinjaSubProject{ subProject, config, move(buildPath) }); } const std::vector& Ninja::getSourceFiles() const { return sourceFiles; } bool Ninja::containsSourceFile(const string &filepath) const { for(const sibs::SourceFile &sourceFile : sourceFiles) { if(sourceFile.filepath == filepath) return true; } return false; } bool Ninja::containsDependency(const string &dependency) const { for(const string &binaryDependency : binaryDependencies) { if(binaryDependency == dependency) return true; } return false; } bool Ninja::containsDynamicDependency(const std::string &dependency) const { for(const string &dynamicDependency : dynamicDependencies) { if(dynamicDependency == dependency) return true; } return false; } Result Ninja::getLinkerFlags(const SibsConfig &config, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallback, GlobalIncludeDirCallbackFunc globalIncludeDirCallback, CflagsCallbackFunc cflagsCallbackFunc) const { const vector &packageListDependencies = config.getPackageListDependencies(); Result globalLibDirResult = getHomeDir(); if (!globalLibDirResult) return Result::Err(globalLibDirResult); FileString globalLibDir = globalLibDirResult.unwrap(); globalLibDir += TINYDIR_STRING("/.cache/sibs/lib"); Result createGlobalLibDirResult = createDirectoryRecursive(globalLibDir.c_str()); if(createGlobalLibDirResult.isErr()) return createGlobalLibDirResult; // If pkg-config is not available then it will be ignored and we check if package instead exists as a global lib (on github etc) vector globalLibDependencies; vector pkgConfigDependencies; for(PackageListDependency *dependency : packageListDependencies) { // PkgConfig libraries, even the static ones are most likely not built statically against libgcc/libc++, so we don't use them if(!config.packaging && PkgConfig::validatePkgConfigPackageVersionExists(dependency)) { pkgConfigDependencies.push_back(dependency); } else { globalLibDependencies.push_back(dependency); } } Result pkgConfigFlagsResult = PkgConfig::getDynamicLibsFlags(pkgConfigDependencies); if (!pkgConfigFlagsResult) { printf("%s, using global lib...\n", pkgConfigFlagsResult.getErrMsg().c_str()); globalLibDependencies.reserve(globalLibDependencies.size() + pkgConfigDependencies.size()); for (PackageListDependency *pkgConfigDependency : pkgConfigDependencies) { globalLibDependencies.push_back(pkgConfigDependency); } pkgConfigDependencies.clear(); } else { const PkgConfigFlags &pkgConfigFlag = pkgConfigFlagsResult.unwrap(); if (!pkgConfigFlag.linkerFlags.empty()) dynamicLinkerFlagCallback(pkgConfigFlag.linkerFlags); if(!pkgConfigFlag.cflags.empty()) cflagsCallbackFunc(pkgConfigFlag.cflags); } return GlobalLib::getLibs(globalLibDependencies, config, globalLibDir, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallback, globalIncludeDirCallback); } Result Ninja::buildSubProjects(LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallback, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { for(auto &subProject : subProjects) { if(subProject.config->getPackageType() == PackageType::EXECUTABLE) { string errMsg = "The sub project "; errMsg += toUtf8(subProject.buildPath); errMsg += " is an executable. Only libraries can be sub projects"; return Result::Err(errMsg); } Result buildResult = subProject.subProject->build(*subProject.config, subProject.buildPath.c_str(), staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallback, globalIncludeDirCallback); if(!buildResult) return buildResult; } return Result::Ok(true); } static vector getCompilerSanitizerFlags(const SibsConfig &config) { if(config.getCompiler() == Compiler::GCC && config.getSanitize()) { return { ninja::NinjaArg::createRaw("-fno-omit-frame-pointer"), ninja::NinjaArg::createRaw("-fsanitize=address"), ninja::NinjaArg::createRaw("-fsanitize=undefined") }; } return {}; } static vector getCompilerOptimizationFlags(const SibsConfig &config) { switch (config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { switch (config.getOptimizationLevel()) { case OPT_LEV_DEBUG: { return { ninja::NinjaArg::createRaw("-Og"), ninja::NinjaArg::createRaw("-g3"), ninja::NinjaArg::createRaw("-D_FORTIFY_SOURCE=2"), ninja::NinjaArg::createRaw("-D_GLIBCXX_ASSERTIONS"), ninja::NinjaArg::createRaw("-fasynchronous-unwind-tables"), ninja::NinjaArg::createRaw("-D_DEBUG") }; } case OPT_LEV_RELEASE: { return { ninja::NinjaArg::createRaw("-O3"), ninja::NinjaArg::createRaw("-g0"), ninja::NinjaArg::createRaw("-DNDEBUG") }; } } break; } case Compiler::MSVC: { switch (config.getOptimizationLevel()) { case OPT_LEV_DEBUG: { return { ninja::NinjaArg::createRaw("/Od"), ninja::NinjaArg::createRaw("/Zi"), ninja::NinjaArg::createRaw("/MTd"), ninja::NinjaArg::createRaw("/D_DEBUG") }; } case OPT_LEV_RELEASE: { return { ninja::NinjaArg::createRaw("/Ox"), ninja::NinjaArg::createRaw("/MT"), ninja::NinjaArg::createRaw("/DNDEBUG") }; } } break; } } return {}; } static string replaceAll(const string &input, const string &from, const string &to) { string result = input; size_t index = 0; while((index = result.find(from, index)) != string::npos) { result.replace(index, from.length(), to); index += to.length(); } return result; } static vector extractIncludesFromCFlag(Compiler compiler, const string &cflag) { vector result; string includeStartStr; switch (compiler) { case Compiler::MINGW_W64: case Compiler::GCC: { includeStartStr = "-I"; break; } case Compiler::MSVC: { includeStartStr = "/I"; break; } default: assert(false); break; } size_t index = 0; while(index != string::npos) { char endChar = ' '; size_t includeStartIndex = cflag.find(includeStartStr, index); if(includeStartIndex == string::npos) break; else if(includeStartIndex > 0) { if(cflag[includeStartIndex - 1] == '"' || cflag[includeStartIndex - 1] == '\'') endChar = cflag[includeStartIndex - 1]; } includeStartIndex += includeStartStr.length(); size_t includeEndIndex = cflag.find(endChar, includeStartIndex); index = includeEndIndex; if(includeEndIndex == string::npos) includeEndIndex = cflag.length(); result.push_back(ninja::NinjaArg::createRaw(cflag.substr(includeStartIndex, includeEndIndex - includeStartIndex))); } return result; } static vector extractIncludesFromCFlags(Compiler compiler, const vector &cflags) { vector result; for(const ninja::NinjaArg &cflag : cflags) { vector subCFlags = extractIncludesFromCFlag(compiler, cflag.arg); result.insert(result.end(), subCFlags.begin(), subCFlags.end()); } return result; } static string convertCIncludeSyntaxToZigInclude(Compiler compiler, const string &cIncludes) { vector result = extractIncludesFromCFlag(compiler, cIncludes); string resultStr; for(ninja::NinjaArg &include : result) { resultStr += " -isystem \"" + include.arg + "\""; } return resultStr; } static vector convertCFlagsIncludesToZigIncludes(Compiler compiler, const vector &cflags) { vector result = extractIncludesFromCFlags(compiler, cflags); for(ninja::NinjaArg &include : result) { include.arg = "-isystem \"" + include.arg + "\""; } 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 == '"') { bool escapeQuote = false; char quoteSymbol = c; ++i; size_t libraryIndexStart = i; while(i < cLibraries.size()) { if(cLibraries[i] == '\\') escapeQuote = !escapeQuote; else if(cLibraries[i] == quoteSymbol && !escapeQuote) break; ++i; } size_t libraryIndexEnd = i; ++i; // This actually means the library path is malformed, but we can fix it here if(escapeQuote) libraryIndexEnd--; if(strncmp(&cLibraries[libraryIndexStart], "-l", 2) == 0) libraryIndexStart += 2; else if(strncmp(&cLibraries[libraryIndexStart], "-pthread", libraryIndexEnd - libraryIndexStart) == 0) continue; size_t libraryPathLength = libraryIndexEnd - libraryIndexStart; if(libraryPathLength > 0) result.push_back(ninja::NinjaArg::createRaw(cLibraries.substr(libraryIndexStart, libraryPathLength))); } else if(c != ' ') { bool escapeSpace = false; size_t libraryIndexStart = i; while(i < cLibraries.size() && !escapeSpace && cLibraries[i] != ' ') { if(cLibraries[i] == '\\') escapeSpace = !escapeSpace; else if(cLibraries[i] == ' ' && !escapeSpace) break; ++i; } size_t libraryIndexEnd = i; ++i; // This actually means the library path is malformed, but we can fix it here if(escapeSpace) libraryIndexEnd--; if(strncmp(&cLibraries[libraryIndexStart], "-l", 2) == 0) libraryIndexStart += 2; else if(strncmp(&cLibraries[libraryIndexStart], "-pthread", libraryIndexEnd - libraryIndexStart) == 0) continue; size_t libraryPathLength = libraryIndexEnd - libraryIndexStart; if(libraryPathLength > 0) result.push_back(ninja::NinjaArg::createRaw(cLibraries.substr(libraryIndexStart, libraryPathLength))); } else ++i; } 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; } static int getFilenameLength(const string &filepath) { for(int i = filepath.size(); i >= 0; --i) { char c = filepath[i]; if(c == '/' || c == '\\') return filepath.size() - i; } return filepath.size(); } static string combineObjectFilesAsZigArgs(const vector &objectFiles) { string result; for(const string &objectFile : objectFiles) { result += " --object " + escape(objectFile); } return result; } #if OS_FAMILY == OS_FAMILY_WINDOWS struct PathSeperatorMatcher { bool operator()(char c) const { return c == '/' || c == '\\'; } }; #else struct PathSeperatorMatcher { bool operator()(char c) const { return c == '/'; } }; #endif static FileString getFileParentDirectory(const FileString &filepath) { auto it = find_if(filepath.rbegin(), filepath.rend(), PathSeperatorMatcher()); if(it == filepath.rend()) return TINYDIR_STRING(""); else return filepath.substr(0, it.base().base() - &filepath[0]); } static string extractDynamicLibDirsFromLinkerFlag(const string &linkerFlag) { string result; FileString flagNative; if(linkerFlag.size() >= 2 && (linkerFlag[0] == '\'' || linkerFlag[0] == '"') && (linkerFlag.back() == '\'' || linkerFlag.back() == '"')) flagNative = toFileString(StringView(&linkerFlag[1], linkerFlag.size() - 2)); else flagNative = toFileString(linkerFlag); if(getFileType(flagNative.c_str()) == FileType::REGULAR) { Result libFullPath = getRealPath(flagNative.c_str()); if(libFullPath) { FileString libDir = getFileParentDirectory(libFullPath.unwrap()); if(!libDir.empty()) { result = toUtf8(libDir); } } } return result; } static string extractDynamicLibDirsFromLinkerFlags(const vector &linkerFlags) { string result; for(const string &flag : linkerFlags) { if(!result.empty()) result += ":"; result += extractDynamicLibDirsFromLinkerFlag(flag); } return result; } static string getCompilerCExecutable(Compiler compiler) { string result; switch(compiler) { case Compiler::GCC: result = "cc"; break; case Compiler::MINGW_W64: result = "x86_64-w64-mingw32-cc"; break; case Compiler::MSVC: result = "cl.exe"; break; } return result; } static string getCompilerCppExecutable(Compiler compiler) { string result; switch(compiler) { case Compiler::GCC: result = "c++"; break; case Compiler::MINGW_W64: result = "x86_64-w64-mingw32-c++"; break; case Compiler::MSVC: result = "cl.exe"; break; } return result; } static string getCompilerLinker(Compiler compiler) { string result; switch(compiler) { case Compiler::GCC: result = "ar"; break; case Compiler::MINGW_W64: result = "x86_64-w64-mingw32-ar"; break; case Compiler::MSVC: result = "lib.exe"; break; } return result; } Result Ninja::build(const SibsConfig &config, const _tinydir_char_t *savePath, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallback, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { bool isCCompilerClang = false; Result cCompilerVersion = exec(TINYDIR_STRING("cc --version")); if(cCompilerVersion && cCompilerVersion.unwrap().exitCode == 0 && cCompilerVersion.unwrap().execStdout.find("clang") != string::npos) isCCompilerClang = true; bool isCppCompilerClang = false; Result cppCompilerVersion = exec(TINYDIR_STRING("c++ --version")); if(cppCompilerVersion && cppCompilerVersion.unwrap().exitCode == 0 && cppCompilerVersion.unwrap().execStdout.find("clang") != string::npos) isCppCompilerClang = true; Result createBuildDirResult = createDirectoryRecursive(savePath); if (!createBuildDirResult) return createBuildDirResult; FileString generatedHeadersDir = config.getProjectPath() + TINYDIR_STRING("/sibs-build/generated-headers"); Result createGeneratedHeadersDirResult = createDirectoryRecursive(generatedHeadersDir.c_str()); if (!createGeneratedHeadersDirResult) return createGeneratedHeadersDirResult; FileString generatedZigHeadersDir = generatedHeadersDir + TINYDIR_STRING("/zig"); Result createGeneratedZigHeadersDirResult = createDirectory(generatedZigHeadersDir.c_str()); if (!createGeneratedZigHeadersDirResult) return createGeneratedZigHeadersDirResult; string generatedZigHeaderDirUtf8 = toUtf8(generatedZigHeadersDir); LibraryType libraryType = getNinjaLibraryType(config.getPackageType()); // TODO: Instead of statically linking everything, maybe we should build everything as they prefer to be built // and then copy the files if they are shared libraries to the same directory as the root project executable // so they can be packaged into an archive that can be distributed? if(config.packaging && !config.isMainProject()) libraryType = LibraryType::STATIC; string savePathUtf8 = toUtf8(savePath); string projectPathUtf8 = toUtf8(config.getProjectPath()); FileString ninjaBuildFilePath = savePath; ninjaBuildFilePath += TINYDIR_STRING("/build.ninja"); string cCompilerName = getCompilerCExecutable(config.getCompiler()); string cppCompilerName = getCompilerCppExecutable(config.getCompiler()); string compilerLinker = getCompilerLinker(config.getCompiler()); ninja::NinjaBuildFile ninjaBuildFile; string globalIncDir; for(const auto &includeDir : config.getIncludeDirs()) { string includeDirRelative = "../../"; includeDirRelative += includeDir; globalIncDir += " "; globalIncDir += getIncludeOptionFlag(config.getCompiler(), includeDirRelative); } auto parentGlobalIncludeDirCallback = globalIncludeDirCallback; for (const string &globalIncludeDir : config.getGlobalIncludeDirs()) { string globalIncludeDirFull = projectPathUtf8; globalIncludeDirFull += "/"; globalIncludeDirFull += globalIncludeDir; if(parentGlobalIncludeDirCallback) parentGlobalIncludeDirCallback(globalIncludeDirFull); } string dependencyExportIncludeDirs = customGlobalIncludeDirs; globalIncludeDirCallback = [&parentGlobalIncludeDirCallback, &globalIncludeDirCallback, &dependencyExportIncludeDirs, &config](const string &globalIncludeDir) { dependencyExportIncludeDirs += " "; dependencyExportIncludeDirs += getIncludeOptionFlag(config.getCompiler(), globalIncludeDir); if (parentGlobalIncludeDirCallback) parentGlobalIncludeDirCallback(globalIncludeDir); }; // TODO: Allow configuring default linking flags. Maybe have `package.useThreads = false` to disable this flag string allLinkerFlags; if(isSamePlatformFamily(config.platform, PLATFORM_LINUX)) allLinkerFlags = "-pthread"; // TODO: Somehow check loading order, because it has to be correct to work.. Or does it for dynamic libraries? // Anyways it's required for static libraries for (const string &binaryDependency : binaryDependencies) { allLinkerFlags += " "; allLinkerFlags += binaryDependency; } vector linkerFlags; vector dynamicLinkerFlags; auto parentProjStaticLinkerFlagCallbackFunc = staticLinkerFlagCallbackFunc; if (!staticLinkerFlagCallbackFunc || libraryType == LibraryType::DYNAMIC) { staticLinkerFlagCallbackFunc = [&linkerFlags](const string &linkerFlag) { linkerFlags.push_back(linkerFlag); }; } auto parentProjDynamicLinkerFlagCallbackFunc = dynamicLinkerFlagCallback; if(!dynamicLinkerFlagCallback || libraryType != LibraryType::STATIC) { dynamicLinkerFlagCallback = [&linkerFlags, &dynamicLinkerFlags, &parentProjDynamicLinkerFlagCallbackFunc](const string &linkerFlag) { if(parentProjDynamicLinkerFlagCallbackFunc) parentProjDynamicLinkerFlagCallbackFunc(linkerFlag); linkerFlags.push_back(linkerFlag); dynamicLinkerFlags.push_back(linkerFlag); }; } for(const string &dynamicDependency : dynamicDependencies) { dynamicLinkerFlagCallback(dynamicDependency); } vector cflags; auto cflagsCallbackFunc = [&cflags](const string &dependencyCflags) { cflags.push_back(ninja::NinjaArg::createRaw(dependencyCflags)); }; Result buildSubProjectResult = buildSubProjects(staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallback, globalIncludeDirCallback); if(!buildSubProjectResult) return buildSubProjectResult; Result linkerFlagsResult = getLinkerFlags(config, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallback, globalIncludeDirCallback, cflagsCallbackFunc); if (linkerFlagsResult.isErr()) return Result::Err(linkerFlagsResult.getErrMsg()); for(vector::reverse_iterator it = linkerFlags.rbegin(), end = linkerFlags.rend(); it != end; ++it) { allLinkerFlags += " "; allLinkerFlags += *it; } globalIncDir += dependencyExportIncludeDirs; globalIncDir += " " + getIncludeOptionFlag(config.getCompiler(), toUtf8(generatedHeadersDir)); ninjaBuildFile.defineGlobalVariable("globalIncDir", globalIncDir); // TODO: Find a better way to convert includes, this could be slow... ninjaBuildFile.defineGlobalVariable("globalIncDirZig", convertCIncludeSyntaxToZigInclude(config.getCompiler(), globalIncDir)); vector defines; for(const auto &definePair : config.getDefines()) { defines.push_back(ninja::NinjaArg::createRaw(getDefineFlag(config.getCompiler(), definePair.first, definePair.second))); } switch (config.platform) { case PLATFORM_WIN64: { for(const string &define : { "WIN64", "_WIN64", "WIN32", "_WIN32" }) { defines.push_back(ninja::NinjaArg::createRaw(getDefineFlag(config.getCompiler(), define, ""))); } break; } case PLATFORM_WIN32: { for(const string &define : { "WIN32", "_WIN32" }) { defines.push_back(ninja::NinjaArg::createRaw(getDefineFlag(config.getCompiler(), define, ""))); } break; } default: break; } if(SYSTEM_PLATFORM == PLATFORM_WIN32 || SYSTEM_PLATFORM == PLATFORM_WIN64) { switch(libraryType) { // TODO: Executable type does not guarantee the executable should be a console on windows. Find a way to define window type as well case LibraryType::EXECUTABLE: defines.push_back(ninja::NinjaArg::createRaw(" _CONSOLE")); break; case LibraryType::STATIC: defines.push_back(ninja::NinjaArg::createRaw(" _LIB")); break; } } // TODO: Verify compilers for language are installed when attempting to build a project that has source files of those types vector compileCCommand; vector compileCppCommand; switch(config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { vector baseCompileCArgs; switch(libraryType) { case LibraryType::EXECUTABLE: { baseCompileCArgs.insert(baseCompileCArgs.end(), { ninja::NinjaArg("ccache"), ninja::NinjaArg(cCompilerName), ninja::NinjaArg("-c"), ninja::NinjaArg("-fpie"), ninja::NinjaArg("$in"), ninja::NinjaArg("-o"), ninja::NinjaArg("$out") }); break; } case LibraryType::STATIC: case LibraryType::DYNAMIC: { baseCompileCArgs.insert(baseCompileCArgs.end(), { ninja::NinjaArg("ccache"), ninja::NinjaArg(cCompilerName), ninja::NinjaArg("-c"), ninja::NinjaArg("-fpic"), ninja::NinjaArg("$in"), ninja::NinjaArg("-o"), ninja::NinjaArg("$out") }); break; } } compileCCommand.insert(compileCCommand.end(), baseCompileCArgs.begin(), baseCompileCArgs.end()); compileCCommand.insert(compileCCommand.end(), { ninja::NinjaArg::createRaw("-fdiagnostics-show-option"), ninja::NinjaArg::createRaw("-fdiagnostics-color=always"), ninja::NinjaArg::createRaw("-pipe"), ninja::NinjaArg::createRaw("-D_FILE_OFFSET_BITS=64"), ninja::NinjaArg::createRaw("-Winvalid-pch"), }); if(config.getCompiler() != Compiler::MINGW_W64) compileCCommand.push_back(ninja::NinjaArg::createRaw("-fstack-protector")); compileCCommand.insert(compileCCommand.end(), { ninja::NinjaArg::createRaw("-MMD"), ninja::NinjaArg::createRaw("$globalIncDir") }); compileCCommand.insert(compileCCommand.end(), cflags.begin(), cflags.end()); compileCCommand.insert(compileCCommand.end(), defines.begin(), defines.end()); if(config.showWarnings) { compileCCommand.insert(compileCCommand.end(), { ninja::NinjaArg("-Wall"), ninja::NinjaArg("-Wextra"), ninja::NinjaArg("-Werror=return-type") }); } else { compileCCommand.push_back(ninja::NinjaArg("-w")); } vector optimizationFlags = getCompilerOptimizationFlags(config); compileCCommand.insert(compileCCommand.end(), optimizationFlags.begin(), optimizationFlags.end()); vector sanitizerFlags = getCompilerSanitizerFlags(config); compileCCommand.insert(compileCCommand.end(), sanitizerFlags.begin(), sanitizerFlags.end()); compileCppCommand = compileCCommand; compileCppCommand[1] = ninja::NinjaArg(cppCompilerName); compileCppCommand.insert(compileCppCommand.end(), { ninja::NinjaArg("-fexceptions"), ninja::NinjaArg("-Wnon-virtual-dtor") }); break; } case Compiler::MSVC: { compileCCommand.insert(compileCCommand.end(), { ninja::NinjaArg::createRaw("cl.exe /showIncludes"), ninja::NinjaArg::createRaw("/c"), ninja::NinjaArg::createRaw("$in"), ninja::NinjaArg::createRaw("/Fo$out"), ninja::NinjaArg::createRaw("$globalIncDir") }); compileCCommand.insert(compileCCommand.end(), cflags.begin(), cflags.end()); compileCCommand.insert(compileCCommand.end(), defines.begin(), defines.end()); if(config.showWarnings) compileCCommand.push_back(ninja::NinjaArg("/Wall")); else compileCCommand.push_back(ninja::NinjaArg("/w")); // TODO: Remove this once locate_windows_sdk has been updated to locate multiple arch windows sdk #if OS_TYPE == OS_TYPE_WINDOWS && defined(SIBS_ENV_32BIT) #error "sibs is currently not supported on windows 32-bit because locate_windows_sdk can only locate x64 windows sdk" #endif switch (SYSTEM_PLATFORM) { case PLATFORM_WIN32: compileCCommand.push_back(ninja::NinjaArg("/MACHINE:X86")); break; case PLATFORM_WIN64: compileCCommand.push_back(ninja::NinjaArg("/MACHINE:X64")); break; } vector optimizationFlags = getCompilerOptimizationFlags(config); compileCCommand.insert(compileCCommand.end(), optimizationFlags.begin(), optimizationFlags.end()); vector sanitizerFlags = getCompilerSanitizerFlags(config); compileCCommand.insert(compileCCommand.end(), sanitizerFlags.begin(), sanitizerFlags.end()); compileCppCommand = compileCCommand; compileCppCommand.push_back(ninja::NinjaArg("/EHs")); break; } } bool zigTest = (config.zigTestAllFiles || !config.zigTestFiles.empty()); vector cLanguageVersionFlags = getLanguageVersionFlag(config.getCompiler(), config.getCversion()); compileCCommand.insert(compileCCommand.end(), cLanguageVersionFlags.begin(), cLanguageVersionFlags.end()); vector cppLanguageVersionFlags = getLanguageVersionFlag(config.getCompiler(), config.getCppVersion()); compileCppCommand.insert(compileCppCommand.end(), cppLanguageVersionFlags.begin(), cppLanguageVersionFlags.end()); ninja::NinjaRule *compileCRule = ninjaBuildFile.createRule("compile_c", compileCCommand); ninja::NinjaRule *compileCppRule = ninjaBuildFile.createRule("compile_cpp", compileCppCommand); switch(config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { compileCRule->depFile = "$out.d"; compileCppRule->depFile = "$out.d"; break; } case Compiler::MSVC: { compileCRule->deps = "msvc"; compileCppRule->deps = "msvc"; break; } } bool crossOsCompiling = !isSamePlatformFamily(config.platform, SYSTEM_PLATFORM); // 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 commonZigArgs = { ninja::NinjaArg::createRaw("-isystem ../../"), ninja::NinjaArg::createRaw("--color on"), ninja::NinjaArg::createRaw("$globalIncDirZig") }; if(isSamePlatformFamily(SYSTEM_PLATFORM, PLATFORM_LINUX) && isSamePlatformFamily(config.platform, PLATFORM_WIN)) commonZigArgs.push_back(ninja::NinjaArg::createRaw("--target-os windows --target-arch x86_64 --target-environ gnu --libc-lib-dir /usr/x86_64-w64-mingw32/lib --libc-static-lib-dir /usr/x86_64-w64-mingw32/lib")); // TODO: Remove this if project does not depend on c libraries or project only has .zig files if(!config.packaging) commonZigArgs.push_back(ninja::NinjaArg::createRaw("--library c")); ninja::NinjaVariable zigHeaderFile("headerFile"); vector zigTestArgs = { ninja::NinjaArg::createRaw("zig test $in --output $out --output-h $headerFile") }; vector compileZigArgs = { ninja::NinjaArg::createRaw("zig build-obj"), ninja::NinjaArg::createRaw("$in"), ninja::NinjaArg::createRaw("--output-h $headerFile"), ninja::NinjaArg::createRaw("--output $out") }; // TODO: Find a way to do this more efficiently vector cflagsIncludes = convertCFlagsIncludesToZigIncludes(config.getCompiler(), cflags); commonZigArgs.insert(commonZigArgs.end(), cflagsIncludes.begin(), cflagsIncludes.end()); if(config.getOptimizationLevel() == sibs::OPT_LEV_RELEASE) { // TODO: Specify a way to change these options, either in project.conf or argument to sibs build commonZigArgs.insert(commonZigArgs.end(), { ninja::NinjaArg::createRaw("--release-safe"), ninja::NinjaArg::createRaw("--strip") }); } zigTestArgs.insert(zigTestArgs.end(), commonZigArgs.begin(), commonZigArgs.end()); compileZigArgs.insert(compileZigArgs.end(), commonZigArgs.begin(), commonZigArgs.end()); if(libraryType == LibraryType::STATIC) compileZigArgs.push_back(ninja::NinjaArg::createRaw("--static")); else if(libraryType == LibraryType::DYNAMIC) // TODO: Verify if this is the correct way to handle dynamic libraries in zig compileZigArgs.push_back(ninja::NinjaArg::createRaw("-rdynamic")); if(zigTest) { // TODO: Verify if we really need to add all libraries for every object file vector zigLibraryFlags = convertCLibrariesToZigLibraries(config.getCompiler(), allLinkerFlags); zigTestArgs.insert(zigTestArgs.end(), zigLibraryFlags.begin(), zigLibraryFlags.end()); } ninja::NinjaRule *compileZigRule = ninjaBuildFile.createRule("compile_zig", compileZigArgs); ninja::NinjaRule *testZigRule = ninjaBuildFile.createRule("zig_test", zigTestArgs); bool usesCFiles = false; bool usesCppFiles = false; bool onlyZigFiles = true; vector objectNames; objectNames.reserve(sourceFiles.size()); vector zigBuilds; // TODO(Urgent): Instead of adding zig builds as dependencies for all c/c++ builds, generate two ninja files; one for zig and one for c/c++. // Then build run zig ninja file first and then c/c++ ninja file. // That would be more efficient as you dont need to add every single zig build as dependency for all c/c++ builds... for(const sibs::SourceFile &sourceFile : sourceFiles) { if(sourceFile.language == sibs::Language::ZIG) { string objectName = config.getPackageName() + "@exe/" + sourceFile.filepath; objectName += getObjectFileExtension(config.getCompiler()); int filenameLength = getFilenameLength(sourceFile.filepath); string zigSourceDirRelative = sourceFile.filepath.substr(0, sourceFile.filepath.size() - filenameLength); FileString zigHeaderFileDir = toFileString(generatedZigHeaderDirUtf8 + '/' + zigSourceDirRelative); Result createZigHeaderFileDirResult = createDirectoryRecursive(zigHeaderFileDir.c_str()); if(!createZigHeaderFileDirResult) return createZigHeaderFileDirResult; string headerName = sourceFile.filepath.substr(sourceFile.filepath.size() - filenameLength, filenameLength - 4) + ".h"; ninja::NinjaArgValue zigHeaderFileValue = { zigHeaderFile, '"' + toUtf8(zigHeaderFileDir) + '/' + headerName + '"' }; ninja::NinjaBuild *ninjaBuild = nullptr; if(zigTest) ninjaBuild = testZigRule->build("../../" + sourceFile.filepath, objectName, { zigHeaderFileValue }); else ninjaBuild = compileZigRule->build("../../" + sourceFile.filepath, objectName, { zigHeaderFileValue }); zigBuilds.push_back(ninjaBuild); objectNames.emplace_back(move(objectName)); } else onlyZigFiles = false; } bool containsZigFiles = false; for(const sibs::SourceFile &sourceFile : sourceFiles) { string objectName; switch(sourceFile.language) { case sibs::Language::C: { objectName += config.getPackageName() + "@exe/" + sourceFile.filepath; objectName += getObjectFileExtension(config.getCompiler()); compileCRule->build("../../" + sourceFile.filepath, objectName, {}, zigBuilds); usesCFiles = true; break; } case sibs::Language::CPP: { objectName += config.getPackageName() + "@exe/" + sourceFile.filepath; objectName += getObjectFileExtension(config.getCompiler()); compileCppRule->build("../../" + sourceFile.filepath, objectName, {}, zigBuilds); usesCppFiles = true; break; } case sibs::Language::ZIG: { // Already built above containsZigFiles = true; break; } default: assert(false); break; } if(!objectName.empty()) objectNames.emplace_back(move(objectName)); } if(containsZigFiles && isSamePlatformFamily(config.platform, PLATFORM_WIN) && !isSamePlatformFamily(SYSTEM_PLATFORM, PLATFORM_WIN)) return Result::Err("Cross compiling a project with zig files from a non-windows platform to windows is currently not supported because zig doesn't support libc when cross compiling"); string packagingFlags; if(config.packaging) { switch(config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { packagingFlags = "-static -static-libgcc -static-libstdc++"; break; } case Compiler::MSVC: { // We always statically link using /MT so there is no need to do it here break; } } } else { if(config.getCompiler() == Compiler::MINGW_W64) { packagingFlags = "-static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic"; } } // TODO: For now zig projects (zig object files) are built with c/c++ compiler, // 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 projectGeneratedBinary; if (!sourceFiles.empty() && !zigTest) { projectGeneratedBinary = "\""; projectGeneratedBinary += savePathUtf8; projectGeneratedBinary += "/"; string noUndefinedFlag; if(!onlyZigFiles) { if(usesCppFiles) noUndefinedFlag = isCppCompilerClang ? "-Wl,-undefined,error" : "-Wl,--no-undefined,--as-needed"; else noUndefinedFlag = isCCompilerClang ? "-Wl,-undefined,error" : "-Wl,--no-undefined,--as-needed"; } ninja::NinjaVariable zigObjectArgs("object_args"); string objectZigArgs; if(onlyZigFiles) objectZigArgs = combineObjectFilesAsZigArgs(objectNames); ninja::NinjaArgValue zigObjectArgsValue = { zigObjectArgs, move(objectZigArgs) }; switch (libraryType) { case LibraryType::EXECUTABLE: { vector buildExeArgs; string executableName = config.getPackageName(); if(isSamePlatformFamily(config.platform, PLATFORM_WIN)) executableName += ".exe"; if(onlyZigFiles) { buildExeArgs.insert(buildExeArgs.end(), { ninja::NinjaArg::createRaw("zig build-exe"), ninja::NinjaArg::createRaw("--name __tmp_zig"), ninja::NinjaArg::createRaw("--output $out"), ninja::NinjaArg::createRaw("$object_args"), }); buildExeArgs.insert(buildExeArgs.end(), commonZigArgs.begin(), commonZigArgs.end()); vector zigLibraryFlags = convertCLibrariesToZigLibraries(config.getCompiler(), allLinkerFlags); buildExeArgs.insert(buildExeArgs.end(), zigLibraryFlags.begin(), zigLibraryFlags.end()); ninja::NinjaRule *buildExeRule = ninjaBuildFile.createRule("build_exec", buildExeArgs); buildExeRule->build(objectNames, executableName, { zigObjectArgsValue }); } else { switch (config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { string rpath = extractDynamicLibDirsFromLinkerFlags(dynamicLinkerFlags); buildExeArgs.insert(buildExeArgs.end(), { ninja::NinjaArg::createRaw("ccache"), ninja::NinjaArg::createRaw(usesCppFiles ? cppCompilerName : cCompilerName), ninja::NinjaArg::createRaw("-o"), ninja::NinjaArg::createRaw("$out"), ninja::NinjaArg::createRaw("$in"), ninja::NinjaArg::createRaw(noUndefinedFlag) }); if(!rpath.empty()) buildExeArgs.push_back(ninja::NinjaArg("-Wl,-rpath," + rpath)); if(config.getSanitize()) { buildExeArgs.insert(buildExeArgs.end(), { ninja::NinjaArg::createRaw("-lasan"), ninja::NinjaArg::createRaw("-lubsan") }); } else if(config.getOptimizationLevel() == OPT_LEV_RELEASE) { // Strip binary buildExeArgs.push_back(ninja::NinjaArg::createRaw("-s")); } // TODO: Add flag to disable -ldl and -lm (dlopen, dlclose, floor, max, ...) #if OS_TYPE == OS_TYPE_OPENBSD || OS_TYPE == OS_TYPE_HAIKU buildExeArgs.insert(buildExeArgs.end(), { ninja::NinjaArg::createRaw("-lm") }); #else if(!isSamePlatformFamily(config.platform, PLATFORM_WIN)) { buildExeArgs.insert(buildExeArgs.end(), { ninja::NinjaArg::createRaw("-ldl"), ninja::NinjaArg::createRaw("-lm") }); } #endif if(config.getCompiler() == Compiler::MINGW_W64) { buildExeArgs.insert(buildExeArgs.end(), { ninja::NinjaArg::createRaw("-lws2_32"), ninja::NinjaArg::createRaw("-lwldap32"), ninja::NinjaArg::createRaw("-lcrypt32"), ninja::NinjaArg::createRaw("-ladvapi32"), ninja::NinjaArg::createRaw("-lgdi32"), ninja::NinjaArg::createRaw("-luser32"), ninja::NinjaArg::createRaw("-luserenv"), ninja::NinjaArg::createRaw("-lopengl32"), ninja::NinjaArg::createRaw("-lglu32"), ninja::NinjaArg::createRaw("-lwinpthread"), ninja::NinjaArg::createRaw("-lshell32") }); } break; } case Compiler::MSVC: { // TODO: Do not link all of these. Find a way to only link the ones that are needed buildExeArgs.insert(buildExeArgs.end(), { ninja::NinjaArg::createRaw(cppCompilerName), ninja::NinjaArg::createRaw("$in"), ninja::NinjaArg::createRaw("/Fe$out"), ninja::NinjaArg::createRaw("Ws2_32.lib"), ninja::NinjaArg::createRaw("Wldap32.lib"), ninja::NinjaArg::createRaw("Crypt32.lib"), ninja::NinjaArg::createRaw("Advapi32.lib"), ninja::NinjaArg::createRaw("Gdi32.lib"), ninja::NinjaArg::createRaw("User32.lib"), ninja::NinjaArg::createRaw("Userenv.lib"), ninja::NinjaArg::createRaw("OpenGL32.lib"), ninja::NinjaArg::createRaw("GlU32.lib"), ninja::NinjaArg::createRaw("Shell32.lib") }); switch (SYSTEM_PLATFORM) { case PLATFORM_WIN32: buildExeArgs.push_back(ninja::NinjaArg::createRaw("/MACHINE:X86")); break; case PLATFORM_WIN64: buildExeArgs.push_back(ninja::NinjaArg::createRaw("/MACHINE:X64")); break; } break; } } if (!allLinkerFlags.empty()) buildExeArgs.push_back(ninja::NinjaArg::createRaw(allLinkerFlags)); buildExeArgs.push_back(ninja::NinjaArg::createRaw(packagingFlags)); ninja::NinjaRule *buildExeRule = ninjaBuildFile.createRule("build_exec", buildExeArgs); buildExeRule->build(objectNames, executableName, {}); } projectGeneratedBinary += executableName; projectGeneratedBinary += "\""; break; } case LibraryType::STATIC: { vector buildStaticArgs; string generatedFile; switch (config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { generatedFile = "lib" + config.getPackageName() + ".a"; break; } case Compiler::MSVC: { generatedFile = config.getPackageName() + ".lib"; break; } } if(onlyZigFiles) { buildStaticArgs.insert(buildStaticArgs.end(), { ninja::NinjaArg::createRaw("zig build-lib --static"), ninja::NinjaArg::createRaw("--name __tmp_zig"), ninja::NinjaArg::createRaw("--output $out"), ninja::NinjaArg::createRaw("$object_args") }); buildStaticArgs.insert(buildStaticArgs.end(), commonZigArgs.begin(), commonZigArgs.end()); ninja::NinjaRule *buildStaticRule = ninjaBuildFile.createRule("build_static", buildStaticArgs); buildStaticRule->build(objectNames, generatedFile, { zigObjectArgsValue }); } else { switch (config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { buildStaticArgs.insert(buildStaticArgs.end(), { ninja::NinjaArg::createRaw(compilerLinker), ninja::NinjaArg::createRaw("rcs"), ninja::NinjaArg::createRaw("$out"), ninja::NinjaArg::createRaw("$in") }); break; } case Compiler::MSVC: { buildStaticArgs.insert(buildStaticArgs.end(), { ninja::NinjaArg::createRaw(compilerLinker), ninja::NinjaArg::createRaw("/OUT:$out"), ninja::NinjaArg::createRaw("$in") }); switch (SYSTEM_PLATFORM) { case PLATFORM_WIN32: buildStaticArgs.push_back(ninja::NinjaArg::createRaw("/MACHINE:X86")); break; case PLATFORM_WIN64: buildStaticArgs.push_back(ninja::NinjaArg::createRaw("/MACHINE:X64")); break; } break; } } ninja::NinjaRule *buildStaticRule = ninjaBuildFile.createRule("build_static", buildStaticArgs); buildStaticRule->build(objectNames, generatedFile, {}); } projectGeneratedBinary += generatedFile; projectGeneratedBinary += "\""; if(parentProjStaticLinkerFlagCallbackFunc) parentProjStaticLinkerFlagCallbackFunc(projectGeneratedBinary); break; } case LibraryType::DYNAMIC: { vector buildDynamicArgs; string generatedFile; switch (config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { generatedFile = "lib" + config.getPackageName() + ".so"; break; } case Compiler::MSVC: { generatedFile = config.getPackageName() + ".dll"; break; } } if(onlyZigFiles) { buildDynamicArgs.insert(buildDynamicArgs.end(), { ninja::NinjaArg::createRaw("zig build-lib --rdynamic"), ninja::NinjaArg::createRaw("--name __tmp_zig"), ninja::NinjaArg::createRaw("--output $out"), ninja::NinjaArg::createRaw("$object_args") }); buildDynamicArgs.insert(buildDynamicArgs.end(), commonZigArgs.begin(), commonZigArgs.end()); vector zigLibraryFlags = convertCLibrariesToZigLibraries(config.getCompiler(), allLinkerFlags); buildDynamicArgs.insert(buildDynamicArgs.end(), zigLibraryFlags.begin(), zigLibraryFlags.end()); ninja::NinjaRule *buildDynamicRule = ninjaBuildFile.createRule("build_dynamic", buildDynamicArgs); buildDynamicRule->build(objectNames, generatedFile, { zigObjectArgsValue }); } else { switch (config.getCompiler()) { case Compiler::MINGW_W64: case Compiler::GCC: { buildDynamicArgs.insert(buildDynamicArgs.end(), { ninja::NinjaArg::createRaw("ccache"), ninja::NinjaArg::createRaw(usesCppFiles ? cppCompilerName : cCompilerName), ninja::NinjaArg::createRaw("$in"), ninja::NinjaArg::createRaw("-shared"), ninja::NinjaArg("-Wl,-soname," + generatedFile), ninja::NinjaArg::createRaw("-o"), ninja::NinjaArg::createRaw("$out"), ninja::NinjaArg::createRaw(noUndefinedFlag) }); if(config.getSanitize()) { buildDynamicArgs.insert(buildDynamicArgs.end(), { ninja::NinjaArg::createRaw("-lasan"), ninja::NinjaArg::createRaw("-lubsan") }); } // TODO: Add flag to disable -ldl and -lm (dlopen, dlclose, floor, max, ...) #if OS_TYPE == OS_TYPE_OPENBSD || OS_TYPE == OS_TYPE_HAIKU buildDynamicArgs.insert(buildDynamicArgs.end(), { ninja::NinjaArg::createRaw("-lm") }); #else if(!isSamePlatformFamily(config.platform, PLATFORM_WIN)) { buildDynamicArgs.insert(buildDynamicArgs.end(), { ninja::NinjaArg::createRaw("-ldl"), ninja::NinjaArg::createRaw("-lm") }); } #endif if(config.getCompiler() == Compiler::MINGW_W64) { buildDynamicArgs.insert(buildDynamicArgs.end(), { ninja::NinjaArg::createRaw("-lws2_32"), ninja::NinjaArg::createRaw("-lwldap32"), ninja::NinjaArg::createRaw("-lcrypt32"), ninja::NinjaArg::createRaw("-ladvapi32"), ninja::NinjaArg::createRaw("-lgdi32"), ninja::NinjaArg::createRaw("-luser32"), ninja::NinjaArg::createRaw("-luserenv"), ninja::NinjaArg::createRaw("-lopengl32"), ninja::NinjaArg::createRaw("-lglu32"), ninja::NinjaArg::createRaw("-lwinpthread"), ninja::NinjaArg::createRaw("-lshell32") }); } break; } case Compiler::MSVC: { buildDynamicArgs.insert(buildDynamicArgs.end(), { ninja::NinjaArg::createRaw(compilerLinker), ninja::NinjaArg::createRaw("/OUT:$out"), ninja::NinjaArg::createRaw("$in"), ninja::NinjaArg::createRaw("Ws2_32.lib"), ninja::NinjaArg::createRaw("Wldap32.lib"), ninja::NinjaArg::createRaw("Crypt32.lib"), ninja::NinjaArg::createRaw("Advapi32.lib"), ninja::NinjaArg::createRaw("Gdi32.lib"), ninja::NinjaArg::createRaw("User32.lib"), ninja::NinjaArg::createRaw("Userenv.lib"), ninja::NinjaArg::createRaw("OpenGL32.lib"), ninja::NinjaArg::createRaw("GlU32.lib"), ninja::NinjaArg::createRaw("Shell32.lib") }); switch (SYSTEM_PLATFORM) { case PLATFORM_WIN32: buildDynamicArgs.push_back(ninja::NinjaArg::createRaw("/MACHINE:X86")); break; case PLATFORM_WIN64: buildDynamicArgs.push_back(ninja::NinjaArg::createRaw("/MACHINE:X64")); break; } break; } } if (!allLinkerFlags.empty()) buildDynamicArgs.push_back(ninja::NinjaArg::createRaw(allLinkerFlags)); buildDynamicArgs.push_back(ninja::NinjaArg::createRaw(packagingFlags)); ninja::NinjaRule *buildDynamicRule = ninjaBuildFile.createRule("build_dynamic", buildDynamicArgs); buildDynamicRule->build(objectNames, generatedFile, {}); } projectGeneratedBinary += generatedFile; projectGeneratedBinary += "\""; if(parentProjDynamicLinkerFlagCallbackFunc) parentProjDynamicLinkerFlagCallbackFunc(projectGeneratedBinary); break; } default: assert(false); return Result::Err("Unexpected error"); } } if(!sourceFiles.empty()) { string result = ninjaBuildFile.generate(); Result fileOverwriteResult = sibs::fileOverwrite(ninjaBuildFilePath.c_str(), sibs::StringView(result.data(), result.size())); if (fileOverwriteResult.isErr()) return fileOverwriteResult; Result buildResult = compile(savePath); if (!buildResult) return buildResult; if(config.isMainProject()) { buildResult = buildCompilationDatabase(savePath, config.getProjectPath()); if(!buildResult) return buildResult; } } Result buildTestResult = buildTests(allLinkerFlags, projectGeneratedBinary, config, savePath, dependencyExportIncludeDirs); if(!buildTestResult) return buildTestResult; return Result::Ok(true); } Result Ninja::buildTests(const std::string &parentLinkerFlags, const std::string &parentGeneratedLib, const SibsConfig &config, const _tinydir_char_t *savePath, const string &parentDependencyExportIncludeDirs) { if(testSourceDir.empty() || !config.shouldBuildTests()) return Result::Ok(true); assert(getNinjaLibraryType(config.getPackageType()) != LibraryType::EXECUTABLE); string parentProjectPathUtf8 = toUtf8(config.getProjectPath()); string parentExportIncludeDirs = parentDependencyExportIncludeDirs; for (const string &parentGlobalIncludeDir : config.getGlobalIncludeDirs()) { string parentExportIncludeDir = parentProjectPathUtf8; parentExportIncludeDir += "/"; parentExportIncludeDir += parentGlobalIncludeDir; parentExportIncludeDirs += " "; parentExportIncludeDirs += getIncludeOptionFlag(config.getCompiler(), parentExportIncludeDir); } #if OS_FAMILY == OS_FAMILY_POSIX FileString testSourceDirNative = testSourceDir; FileString projectConfFilePath = testSourceDir; #else FileString testSourceDirNative = utf8To16(testSourceDir); FileString projectConfFilePath = testSourceDirNative; #endif projectConfFilePath += TINYDIR_STRING("/project.conf"); FileType projectConfFileType = getFileType(projectConfFilePath.c_str()); SibsTestConfig sibsTestConfig(config.getCompiler(), testSourceDirNative, config.getOptimizationLevel()); sibsTestConfig.platform = config.platform; 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); if(!result) return result; } backend::Ninja ninja; ninja.addGlobalIncludeDirs(parentExportIncludeDirs); if(!parentLinkerFlags.empty()) ninja.addDependency(parentLinkerFlags); switch(config.getPackageType()) { case PackageType::STATIC: ninja.addDependency(parentGeneratedLib); break; case PackageType::LIBRARY: case PackageType::DYNAMIC: ninja.addDynamicDependency(parentGeneratedLib); break; default: break; } bool zigTest = false; if(config.zigTestAllFiles) { backend::BackendUtils::collectSourceFiles(testSourceDirNative.c_str(), &ninja, sibsTestConfig); // 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, toUtf8(testFile.c_str()).c_str()); } zigTest = true; } else { backend::BackendUtils::collectSourceFiles(testSourceDirNative.c_str(), &ninja, sibsTestConfig); } if(!ninja.getSourceFiles().empty()) { FileString buildPath = testSourceDirNative; switch(sibsTestConfig.getOptimizationLevel()) { case OPT_LEV_DEBUG: buildPath += TINYDIR_STRING("/sibs-build/debug"); break; case OPT_LEV_RELEASE: buildPath += TINYDIR_STRING("/sibs-build/release"); break; } Result buildFileResult = ninja.build(sibsTestConfig, buildPath.c_str()); if (!buildFileResult) return buildFileResult; // Main projects test should also have compilation database, so we can use it inside IDE if(config.isMainProject()) { buildFileResult = buildCompilationDatabase(buildPath.c_str(), testSourceDirNative); if(!buildFileResult) return buildFileResult; } 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); } } return Result::Ok(true); } Result Ninja::compile(const _tinydir_char_t *buildFilePath) { #if OS_TYPE == OS_TYPE_LINUX FileString command = TINYDIR_STRING("script -eqc 'ninja -C \""); command += buildFilePath; command += TINYDIR_STRING("\"' /dev/null"); #else FileString command = TINYDIR_STRING("ninja -C \""); command += buildFilePath; command += TINYDIR_STRING("\""); #endif Result execResult = exec(command.c_str(), true); if(execResult.isOk()) { if(execResult.unwrap().exitCode == 0) { //printf("%s\n", execResult.unwrap().execStdout.c_str()); return Result::Ok(true); } else return Result::Err(""); } else return Result::Err(""); } Result Ninja::buildCompilationDatabase(const _tinydir_char_t *buildFilePath, const FileString &saveDir) { Result compdbAvailableResult = exec(TINYDIR_STRING("compdb version"), false); bool isCompdbAvailable = (compdbAvailableResult && compdbAvailableResult.unwrap().exitCode == 0); FileString command = TINYDIR_STRING("ninja -C \""); command += buildFilePath; command += TINYDIR_STRING("\" -t compdb compile_c compile_cpp > \""); command += (isCompdbAvailable ? buildFilePath : saveDir); command += TINYDIR_STRING("/compile_commands.json\""); Result execResult = exec(command.c_str(), false); if(execResult) { if(execResult.unwrap().exitCode != 0) { string errMsg = "Failed to build compilation database, reason: "; errMsg += execResult.unwrap().execStdout; return Result::Err(errMsg); } } else { string errMsg = "Failed to build compilation database, reason: "; errMsg += execResult.getErrMsg(); return Result::Err(errMsg); } if(isCompdbAvailable) { command = TINYDIR_STRING("compdb -p \""); command += buildFilePath; command += TINYDIR_STRING("\" list > \""); command += saveDir; command += TINYDIR_STRING("/compile_commands.json\""); execResult = exec(command.c_str(), false); if(execResult) { if(execResult.unwrap().exitCode != 0) { string errMsg = "Failed to build compilation database, reason: "; errMsg += execResult.unwrap().execStdout; return Result::Err(errMsg); } } else { string errMsg = "Failed to build compilation database, reason: "; errMsg += execResult.getErrMsg(); return Result::Err(errMsg); } } return Result::Ok(true); } }