/
redgpu
/
ezEngine
Обзор
Документация
Войти
/
redgpu
/
ezEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
Code/Tools/DependencyAnalysis/DependencyAnalysis.cpp
1 153 строки
32 KB
Jan Krassnigg
Cleanup of ezWindow infrastructure (#1685)
14 окт 2025, 19:16
Не верифицирован
14 окт 2025, 19:16
495a1fe
Код
Авторство
О чём код?
#include <Foundation/Application/Application.h> #include <Foundation/CodeUtils/Tokenizer.h> #include <Foundation/Configuration/Startup.h> #include <Foundation/Containers/HashSet.h> #include <Foundation/Containers/HashTable.h> #include <Foundation/Containers/Map.h> #include <Foundation/Containers/Set.h> #include <Foundation/IO/FileSystem/DataDirTypeFolder.h> #include <Foundation/IO/FileSystem/FileReader.h> #include <Foundation/IO/FileSystem/FileSystem.h> #include <Foundation/IO/JSONReader.h> #include <Foundation/Logging/ConsoleWriter.h> #include <Foundation/Logging/HTMLWriter.h> #include <Foundation/Logging/Log.h> #include <Foundation/Logging/VisualStudioWriter.h> #include <Foundation/Memory/LinearAllocator.h> #include <Foundation/Strings/PathUtils.h> #include <Foundation/Strings/String.h> #include <Foundation/Strings/StringBuilder.h> #include <Foundation/System/SystemInformation.h> #include <Foundation/Threading/TaskSystem.h> #include <Foundation/Types/UniquePtr.h> class ezDependencyAnalysisApp; bool g_bVerbose = false; namespace { using DependencyListType = ezHashSet<ezString>; struct CompilationUnitContext { DependencyListType m_dependencies; ezDynamicArray<ezString> m_IncludeDirectories; ezHashTable<ezString, ezString> m_includeCache; }; using CompilationUnitListType = ezHashTable<ezString, CompilationUnitContext>; EZ_ALWAYS_INLINE void SkipWhitespace(ezToken& ref_token, ezUInt32& i, const ezDeque<ezToken>& tokens) { while (ref_token.m_iType == ezTokenType::Whitespace) { ref_token = tokens[++i]; } } EZ_ALWAYS_INLINE void SkipLine(ezToken& ref_token, ezUInt32& i, const ezDeque<ezToken>& tokens) { while (ref_token.m_iType != ezTokenType::Newline && ref_token.m_iType != ezTokenType::EndOfFile) { ref_token = tokens[++i]; } } // Json parser for command_line.json files generated by cmake class CompileCommandsParser : public ezJSONParser { public: struct CompileCommand { ezString command; ezString file; }; void Initialize(ezStreamReader& inout_stream) { SetInputStream(inout_stream); } ezResult GetNextCommand(CompileCommand& out_command) { while (!m_bObjectOpen) { if (!ContinueParsing()) { return EZ_FAILURE; } } while (m_bObjectOpen) { if (!ContinueParsing()) { return EZ_FAILURE; } } out_command = m_Command; return EZ_SUCCESS; } private: // Inherited via ezJSONParser virtual bool OnVariable(ezStringView sVarName) override { bool needed = false; if (sVarName == "file") { needed = true; m_pNextVar = &m_Command.file; } else if (sVarName == "command") { needed = true; m_pNextVar = &m_Command.command; } if (!needed && sVarName != "directory" && sVarName != "output") { ezStringBuilder fmt; fmt.SetFormat("Unknown variable '{0}'", sVarName); ParsingError(fmt.GetView(), false); } return needed; } virtual void OnReadValue(ezStringView sValue) override { if (m_pNextVar == nullptr) { ParsingError("Unexpected value", true); return; } *m_pNextVar = sValue; m_pNextVar = nullptr; } virtual void OnReadValue(double fValue) override { } virtual void OnReadValue(bool bValue) override { } virtual void OnReadValueNULL() override { } virtual void OnBeginObject() override { if (!m_bOuterArrayOpen || m_bObjectOpen) { ParsingError("Unexpected object begin", true); return; } m_bObjectOpen = true; m_Command = {}; } virtual void OnEndObject() override { if (!m_bObjectOpen) { ParsingError("Unexpected object end", true); return; } m_bObjectOpen = false; } virtual void OnBeginArray() override { if (!m_bOuterArrayOpen) { m_bOuterArrayOpen = true; } else { ParsingError("Unexpected array begin", true); } } virtual void OnEndArray() override { if (m_bOuterArrayOpen) { m_bOuterArrayOpen = false; } else { ParsingError("Unexpected array end", true); } } bool m_bOuterArrayOpen = false; bool m_bObjectOpen = false; ezString* m_pNextVar = nullptr; CompileCommand m_Command; }; // ezOSFile::ExistsFile is quite slow, cache the results thread local to avoid lock contetion struct ExistsFileCache { static thread_local ezHashTable<ezString, bool> m_ExistsFileCache; static bool ExistsFile(ezStringView sFile) { bool existedAlready = false; bool returnValue = false; { bool& result = m_ExistsFileCache.FindOrAdd(sFile, &existedAlready); if (!existedAlready) { result = ezOSFile::ExistsFile(sFile); } returnValue = result; } return returnValue; } }; thread_local ezHashTable<ezString, bool> ExistsFileCache::m_ExistsFileCache; // Task to parse a single header file // stores the results in the task, need to be extracted after finish using MoveOutDependencies class ParseHeaderFileTask : public ezTask { public: ParseHeaderFileTask() { ConfigureTask("ParseHeaderFileTask", ezTaskNesting::Maybe); } void Initialize(ezStringView sPath) { m_sPath = sPath; } virtual void Execute() override; ezStringView GetPath() const { return m_sPath; } ezHashSet<ezString>&& MoveOutDependencies() { return std::move(m_Dependencies); } private: ezString m_sPath; ezHashSet<ezString> m_Dependencies; ezTaskGroupID m_TaskGroup; }; // Task to collect the dependencies of a single compilation unit // Might need to run multiple times (call HasWorkLeft to know if it needs to be executed again) // Needs to spawn other tasks when HasWorkLeft returns true: see ScheduleRemainingWork class CollectDependenciesTask : public ezTask { public: CollectDependenciesTask() { ConfigureTask("CollectDependenciesTask", ezTaskNesting::Maybe); } void Initialize(ezStringView sPath, CompilationUnitContext& ref_context, ezDependencyAnalysisApp& ref_app) { m_sPath = sPath; m_pContext = &ref_context; m_pApp = &ref_app; } void OnParsingFinished(ezTaskGroupID taskGroupID); virtual void Execute() override; bool HasWorkLeft() const { return m_LeftoverFiles.GetCount() > 0; } void ScheduleRemainingWork(); private: ezStringView m_sPath; CompilationUnitContext* m_pContext = nullptr; ezDependencyAnalysisApp* m_pApp = nullptr; ezDynamicArray<ezSharedPtr<ParseHeaderFileTask>> m_ParseTasks; ezDynamicArray<ezString> m_LeftoverFiles; ezTaskGroupID m_ParseTaskGroup; }; } // namespace /* * This application finds all dependencies of a given compile setup. * * Currently requires a compile_commands.json generated with cmake targeting the clang compiler * * Basic flow * - Parse the compile_command.json to find all .cpp files (compilation units) * - Parse each compilation unit, and find all headers it includes * - Parse all found headers and note their dependencies (storing only the unresolved paths) * - For each compilation unit resolve all dependencies by doing a breadth first search in parallel * + Whenever we encounter a header file we have not seen yet, we need to parse it * - Write out the results into a json file. For each compilation units all header dependencies will be listed. */ class ezDependencyAnalysisApp : public ezApplication { private: friend CollectDependenciesTask; ezString m_sCompileCommandsPath; bool m_bHadErrors; bool m_bHadSeriousWarnings; bool m_bHadWarnings; ezDynamicArray<ezString> m_IgnorePatterns; ezString m_sOutputPath; CompilationUnitListType m_CompilationUnits; ezMutex m_HeaderDependenciesMutex; ezHashTable<ezString, DependencyListType> m_HeaderDependencies; ezMutex m_HeaderParsingScheduledMutex; ezHashSet<ezString> m_HeaderParsingScheduled; public: using SUPER = ezApplication; ezDependencyAnalysisApp() : ezApplication("DependencyAnalysis") { m_bHadErrors = false; m_bHadSeriousWarnings = false; m_bHadWarnings = false; } /// Makes sure the apps return value reflects whether there were any errors or warnings static void LogInspector(const ezLoggingEventData& eventData) { ezDependencyAnalysisApp* app = (ezDependencyAnalysisApp*)ezApplication::GetApplicationInstance(); switch (eventData.m_EventType) { case ezLogMsgType::ErrorMsg: app->m_bHadErrors = true; break; case ezLogMsgType::SeriousWarningMsg: app->m_bHadSeriousWarnings = true; break; case ezLogMsgType::WarningMsg: app->m_bHadWarnings = true; break; default: break; } } ezResult ParseArray(const ezVariant& value, ezHashSet<ezString>& ref_dst) { if (!value.CanConvertTo<ezVariantArray>()) { ezLog::Error("Expected array"); return EZ_FAILURE; } auto a = value.Get<ezVariantArray>(); const auto arraySize = a.GetCount(); for (ezUInt32 i = 0; i < arraySize; i++) { auto& el = a[i]; if (!el.CanConvertTo<ezString>()) { ezLog::Error("Value {0} at index {1} can not be converted to a string. Expected array of strings.", el, i); return EZ_FAILURE; } ezStringBuilder file = el.Get<ezString>(); file.ToLower(); ref_dst.Insert(file); } return EZ_SUCCESS; } virtual void AfterCoreSystemsStartup() override { ezGlobalLog::AddLogWriter(ezLogWriter::Console::LogMessageHandler); ezGlobalLog::AddLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); ezGlobalLog::AddLogWriter(LogInspector); ezSystemInformation info = ezSystemInformation::Get(); const ezInt32 iCpuCores = info.GetCPUCoreCount(); ezTaskSystem::SetWorkerThreadCount(iCpuCores); if (GetArgumentCount() < 2) { ezLog::Info("DependencyAnalysis.exe [options] path/to/compile_commands.json"); ezLog::Info(""); ezLog::Info("-i [pattern] --ignorePattern [pattern] ignore all cpp/h files containing this pattern"); ezLog::Info("-v --verbose enable verbose logging"); ezLog::Info("-o [path] --output [path] path to the output json file"); ezLog::Info(""); ezLog::Error("No command line arguments given."); return; } // Add the empty data directory to access files via absolute paths ezFileSystem::AddDataDirectory("", "App", ":", ezDataDirUsage::AllowWrites).IgnoreResult(); // pass the absolute path to the directory that should be scanned as the first parameter to this application ezStringBuilder sCompileCommandsPath; auto numArgs = GetArgumentCount(); auto shortIgnore = ezStringView("-i"); auto longIgnore = ezStringView("--ignorePattern"); auto shortVerbose = ezStringView("-v"); auto longVerbose = ezStringView("--verbose"); auto shortOutput = ezStringView("-o"); auto longOutput = ezStringView("--output"); for (ezUInt32 argi = 1; argi < numArgs; argi++) { auto arg = ezStringView(GetArgument(argi)); if (arg == shortIgnore || arg == longIgnore) { if (numArgs <= argi + 1) { ezLog::Error("Missing path for {0}", arg); return; } ezStringBuilder ignorePattern = GetArgument(argi + 1); if (ignorePattern == shortIgnore || ignorePattern == longIgnore || ignorePattern == shortIgnore || ignorePattern == longIgnore || ignorePattern == shortOutput || ignorePattern == longOutput) { ezLog::Error("Missing path for {0} found {1} instead", arg, ignorePattern.GetView()); return; } argi++; ignorePattern.MakeCleanPath(); m_IgnorePatterns.PushBack(ignorePattern); } else if (arg == shortVerbose || arg == longVerbose) { g_bVerbose = true; } else if (arg == shortOutput || arg == longOutput) { if (!m_sOutputPath.IsEmpty()) { ezLog::Error("Output path given multiple times"); return; } if (numArgs <= argi + 1) { ezLog::Error("Missing path for {0}", arg); return; } ezStringBuilder outputPath = GetArgument(argi + 1); if (outputPath == shortIgnore || outputPath == longIgnore || outputPath == shortIgnore || outputPath == longIgnore || outputPath == shortOutput || outputPath == longOutput) { ezLog::Error("Missing path for {0} found {1} instead", arg, outputPath.GetView()); return; } if (!outputPath.IsAbsolutePath()) { outputPath = ezOSFile::GetCurrentWorkingDirectory(); outputPath.AppendPath(GetArgument(argi + 1)); } outputPath.MakeCleanPath(); argi++; m_sOutputPath = outputPath; } else { if (sCompileCommandsPath.IsEmpty()) { sCompileCommandsPath = arg; sCompileCommandsPath.MakeCleanPath(); } else { ezLog::Error("Currently only one directory is supported for searching. Did you forget -i|--includeDir? {0}", arg); } } } if (!ezPathUtils::IsAbsolutePath(sCompileCommandsPath.GetData())) ezLog::Error("The given path is not absolute: '{0}'", sCompileCommandsPath); m_sCompileCommandsPath = sCompileCommandsPath; if (m_sOutputPath.IsEmpty()) { ezLog::Error("No output path given. Use -o or --output"); } } virtual void BeforeCoreSystemsShutdown() override { SetReturnCode(0); ezGlobalLog::RemoveLogWriter(LogInspector); ezGlobalLog::RemoveLogWriter(ezLogWriter::Console::LogMessageHandler); ezGlobalLog::RemoveLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); } static ezResult ReadEntireFile(ezStringView sFile, ezStringBuilder& out_sContents) { out_sContents.Clear(); ezFileReader File; if (File.Open(sFile) == EZ_FAILURE) { ezLog::Error("Could not open for reading: '{0}'", sFile); return EZ_FAILURE; } ezDynamicArray<ezUInt8> FileContent; ezUInt8 Temp[4024]; ezUInt64 uiRead = File.ReadBytes(Temp, EZ_ARRAY_SIZE(Temp)); while (uiRead > 0) { FileContent.PushBackRange(ezArrayPtr<ezUInt8>(Temp, (ezUInt32)uiRead)); uiRead = File.ReadBytes(Temp, EZ_ARRAY_SIZE(Temp)); } FileContent.PushBack(0); if (!ezUnicodeUtils::IsValidUtf8((const char*)&FileContent[0])) { ezLog::Error("The file \"{0}\" contains characters that are not valid Utf8. This often happens when you type special characters in " "an editor that does not save the file in Utf8 encoding.", sFile); return EZ_FAILURE; } out_sContents = (const char*)&FileContent[0]; return EZ_SUCCESS; } static ezResult FindIncludeFile(ezStringView sPath, CompilationUnitContext& inout_context, ezStringBuilder& out_sPath) { if (sPath.IsAbsolutePath()) { out_sPath = sPath; out_sPath.MakeCleanPath(); if (ExistsFileCache::ExistsFile(sPath)) { return EZ_SUCCESS; } else { return EZ_FAILURE; } } for (auto& includeDir : inout_context.m_IncludeDirectories) { out_sPath = includeDir; out_sPath.AppendPath(sPath); out_sPath.MakeCleanPath(); if (ExistsFileCache::ExistsFile(out_sPath)) { return EZ_SUCCESS; } } return EZ_FAILURE; } bool IgnorePath(const ezStringBuilder& sBuilder) { for (auto& ignore : m_IgnorePatterns) { if (sBuilder.FindSubString(ignore)) { return true; } } return false; } void ParseCompileCommandsJson() { ezFileReader reader; reader.Open(m_sCompileCommandsPath).AssertSuccess(); // TODO error handling CompileCommandsParser parser; parser.SetLogInterface(ezLog::GetThreadLocalLogSystem()); parser.Initialize(reader); ezDynamicArray<ezStringView> commandParts; ezStringBuilder tmpPath; CompileCommandsParser::CompileCommand command; while (parser.GetNextCommand(command).Succeeded()) { if (g_bVerbose) { ezLog::Info("file: {0}", command.file); } tmpPath = command.file; tmpPath.MakeCleanPath(); if (IgnorePath(tmpPath)) { continue; } if (tmpPath.GetFileExtension().Compare_NoCase("cpp") != 0) { continue; } auto& context = m_CompilationUnits[tmpPath]; ezDynamicArray<ezString> includeFiles; command.command.Split(false, commandParts, " "); const ezUInt32 numParts = commandParts.GetCount(); for (ezUInt32 i = 0; i < numParts; i++) { auto part = commandParts[i]; if (part.Compare_NoCase("-isystem") == 0) { i++; part = commandParts[i]; tmpPath = part; tmpPath.MakeCleanPath(); if (!IgnorePath(tmpPath)) // TODO make command line parameter { if (g_bVerbose) { ezLog::Info(tmpPath); } context.m_IncludeDirectories.PushBack(tmpPath); } } else if (part.TrimWordStart("-include")) { tmpPath = part; tmpPath.MakeCleanPath(); if (g_bVerbose) { ezLog::Info(tmpPath); } includeFiles.PushBack(tmpPath); } else if (part.TrimWordStart("-I")) { tmpPath = part; tmpPath.MakeCleanPath(); if (!IgnorePath(tmpPath)) { if (g_bVerbose) { ezLog::Info(tmpPath); } context.m_IncludeDirectories.PushBack(tmpPath); } } } for (auto& includeFile : includeFiles) { if (FindIncludeFile(includeFile.GetView(), context, tmpPath).Succeeded()) { context.m_dependencies.Insert(tmpPath); } else { if (g_bVerbose) { ezLog::Warning("Could not find include file '{0}' specified on the command line in any include path", includeFile); } } } } } void ParseCompilationUnits() { struct CompUnit { ezStringView path; CompilationUnitContext* context; }; ezDynamicArray<CompUnit> compilationUnits; compilationUnits.Reserve(m_CompilationUnits.GetCount()); for (auto& compilationUnit : m_CompilationUnits) { compilationUnits.PushBack({compilationUnit.Key().GetView(), &compilationUnit.Value()}); } ezTaskSystem::ParallelFor( compilationUnits.GetArrayPtr(), [this](ezArrayPtr<CompUnit> compUnits) { ezStringBuilder tmpPath; DependencyListType dependencies; for (auto& compUnit : compUnits) { ezStringView currentFile = compUnit.path; EZ_LOG_BLOCK("CPP", currentFile); dependencies.Clear(); FindDependencies(currentFile, dependencies); for (auto& dependency : dependencies) { if (!dependency.IsAbsolutePath()) { if (FindIncludeFile(dependency.GetView(), *compUnit.context, tmpPath).Succeeded()) { compUnit.context->m_dependencies.Insert(tmpPath); } else { if (g_bVerbose) { ezLog::Warning("Warning dependency '{0}' of '{1}' not found", dependency, currentFile); } } } } } }, "ParseCompilationUnit"); } void ParseHeaderFiles() { ezStringBuilder tmpPath; ezDynamicArray<ezSharedPtr<ParseHeaderFileTask>> filesToCheckTasks; auto taskGroup = ezTaskSystem::CreateTaskGroup(ezTaskPriority::EarlyThisFrame); for (auto& compilationUnit : m_CompilationUnits) { for (auto& dependency : compilationUnit.Value().m_dependencies) { bool existed = false; if (auto& dependencies = m_HeaderDependencies.FindOrAdd(dependency.GetView(), &existed); !existed) { filesToCheckTasks.PushBack(EZ_DEFAULT_NEW(ParseHeaderFileTask)); filesToCheckTasks.PeekBack()->Initialize(dependency.GetView()); ezTaskSystem::AddTaskToGroup(taskGroup, filesToCheckTasks.PeekBack()); } } } ezTaskSystem::StartTaskGroup(taskGroup); ezTaskSystem::WaitForGroup(taskGroup); for (auto& task : filesToCheckTasks) { m_HeaderDependencies[task->GetPath()] = task->MoveOutDependencies(); } } void CheckDependentFiles() { ezDynamicArray<ezSharedPtr<CollectDependenciesTask>> tasks; for (auto& compilationUnit : m_CompilationUnits) { tasks.PushBack(EZ_DEFAULT_NEW(CollectDependenciesTask)); tasks.PeekBack()->Initialize(compilationUnit.Key(), compilationUnit.Value(), *this); } const ezUInt32 totalNumTasks = tasks.GetCount(); // First iteration { auto group = ezTaskSystem::CreateTaskGroup(ezTaskPriority::EarlyThisFrame); for (auto& task : tasks) { ezTaskSystem::AddTaskToGroup(group, task); } ezTaskSystem::StartTaskGroup(group); ezTaskSystem::WaitForGroup(group); } while (true) { for (ezUInt32 i = 0; i < tasks.GetCount();) { if (tasks[i]->HasWorkLeft()) { tasks[i]->ScheduleRemainingWork(); ++i; } else { tasks.RemoveAtAndSwap(i); } } if (tasks.GetCount() == 0) // If none of the tasks had work left, we are done { break; } ezLog::Info("Collecting dependencies. Remaining {0} of {1} total", tasks.GetCount(), totalNumTasks); ezTaskSystem::FinishFrameTasks(); // Wait for parsing tasks to finish before doing the next round of dependency collection auto group = ezTaskSystem::CreateTaskGroup(ezTaskPriority::EarlyThisFrame); for (auto& task : tasks) { ezTaskSystem::AddTaskToGroup(group, task); } ezTaskSystem::StartTaskGroup(group); ezTaskSystem::WaitForGroup(group); } } void WriteOutResults() { ezFileWriter FileOut; if (FileOut.Open(m_sOutputPath) == EZ_FAILURE) { ezLog::Error("Could not open the file for writing: '{0}'", "results.json"); return; } else { ezStringBuilder json; json.Append("{\n"); json.Append(" \"files\" : [\n"); auto processFile = [this, &json](CompilationUnitListType::Iterator& file) { json.Append(" {\n"); json.AppendFormat(" \"name\" : \"{0}\",\n", file.Key()); json.Append(" \"dependencies\" :\n"); json.Append(" [\n"); auto count = (*file).Value().m_dependencies.GetCount(); if (count > 0) { count--; auto depIt = (*file).Value().m_dependencies.GetIterator(); for (ezUInt32 i = 0; i < count; ++i, depIt.Next()) { json.AppendFormat(" \"{0}\",\n", (*depIt).GetView()); } json.AppendFormat(" \"{0}\"\n", (*depIt).GetView()); } json.Append(" ]\n"); json.Append(" }"); }; auto count = m_CompilationUnits.GetCount(); if (count > 0) { count--; auto fileIt = m_CompilationUnits.GetIterator(); for (ezUInt32 i = 0; i < count; ++i, fileIt.Next()) { processFile(fileIt); json.Append(",\n"); } processFile(fileIt); json.Append("\n"); } json.Append(" ]\n"); json.Append("}\n"); FileOut.WriteBytes(json.GetData(), json.GetElementCount()).AssertSuccess(); } } static void FindDependencies(ezStringView sCurrentFile, DependencyListType& inout_dependencies) { ezStringBuilder fileContents; ReadEntireFile(sCurrentFile, fileContents).IgnoreResult(); auto fileDir = sCurrentFile.GetFileDirectory(); ezTokenizer tokenizer; auto dataView = fileContents.GetView(); auto start = dataView.GetStartPointer(); auto elementCount = dataView.GetElementCount(); if (elementCount >= 3 && ezUnicodeUtils::SkipUtf8Bom(start)) { elementCount -= 3; } tokenizer.Tokenize(ezArrayPtr<const ezUInt8>(reinterpret_cast<const ezUInt8*>(start), elementCount), ezLog::GetThreadLocalLogSystem()); ezStringView hash("#"); ezStringView include("include"); ezStringView openAngleBracket("<"); ezStringView closeAngleBracket(">"); auto tokens = tokenizer.GetTokens(); const auto numTokens = tokens.GetCount(); for (ezUInt32 i = 0; i < numTokens; i++) { auto curToken = tokens[i]; while (curToken.m_iType == ezTokenType::Whitespace) { curToken = tokens[++i]; } if (curToken.m_iType == ezTokenType::NonIdentifier && curToken.m_DataView == hash) { do { curToken = tokens[++i]; } while (curToken.m_iType == ezTokenType::Whitespace); if (curToken.m_iType == ezTokenType::Identifier && curToken.m_DataView == include) { auto includeToken = curToken; do { curToken = tokens[++i]; } while (curToken.m_iType == ezTokenType::Whitespace); if (curToken.m_iType == ezTokenType::String1) { // #include "bla" ezStringBuilder absIncludePath; ezStringBuilder relativePath; relativePath = curToken.m_DataView; relativePath.Trim("\""); relativePath.MakeCleanPath(); absIncludePath = fileDir; absIncludePath.AppendPath(relativePath); if (!ExistsFileCache::ExistsFile(absIncludePath)) { inout_dependencies.Insert(relativePath); } else { inout_dependencies.Insert(absIncludePath); } } else if (curToken.m_iType == ezTokenType::NonIdentifier && curToken.m_DataView == openAngleBracket) { // #include <bla> bool error = false; auto startToken = curToken; do { curToken = tokens[++i]; if (curToken.m_iType == ezTokenType::Newline) { ezLog::Error("Non-terminated '<' in #include {0} line {1}", sCurrentFile, includeToken.m_uiLine); error = true; break; } } while (curToken.m_iType != ezTokenType::NonIdentifier || curToken.m_DataView != closeAngleBracket); if (error) { // in case of error skip the malformed line in hopes that we can recover from the error. do { curToken = tokens[++i]; } while (curToken.m_iType != ezTokenType::Newline); } else { ezStringBuilder includePath; includePath = ezStringView(startToken.m_DataView.GetEndPointer(), curToken.m_DataView.GetStartPointer()); includePath.MakeCleanPath(); inout_dependencies.Insert(includePath); } } else { // error ezLog::Error("Can not parse #include statement in {0} line {1}", sCurrentFile, includeToken.m_uiLine); } } else { while (curToken.m_iType != ezTokenType::Newline && curToken.m_iType != ezTokenType::EndOfFile) { curToken = tokens[++i]; } } } else { while (curToken.m_iType != ezTokenType::Newline && curToken.m_iType != ezTokenType::EndOfFile) { curToken = tokens[++i]; } } } } virtual void Run() override { // something basic has gone wrong if (m_bHadSeriousWarnings || m_bHadErrors) { QuitApplication(); return; } auto start = ezTimestamp::CurrentTimestamp(); if (g_bVerbose) { ezLog::Info("Parse compile commands json"); } ParseCompileCommandsJson(); auto parseJsonEnd = ezTimestamp::CurrentTimestamp(); if (g_bVerbose) { ezLog::Info("Parse compilation units"); } ParseCompilationUnits(); auto parseCompilationUnitsEnd = ezTimestamp::CurrentTimestamp(); if (g_bVerbose) { ezLog::Info("Parse header files"); } ParseHeaderFiles(); auto parseHeaderFilesEnd = ezTimestamp::CurrentTimestamp(); if (g_bVerbose) { ezLog::Info("Build dependencies"); } CheckDependentFiles(); auto checkDependendtFilesEnd = ezTimestamp::CurrentTimestamp(); if (g_bVerbose) { ezLog::Info("Write out results"); } WriteOutResults(); auto writeOutResultsEnd = ezTimestamp::CurrentTimestamp(); ezLog::Info("Time to parse compile commands json: {0}s", (parseJsonEnd - start).AsFloatInSeconds()); ezLog::Info("Time to parse compilation units: {0}s", (parseCompilationUnitsEnd - parseJsonEnd).AsFloatInSeconds()); ezLog::Info("Time to parse header files: {0}s", (parseHeaderFilesEnd - parseCompilationUnitsEnd).AsFloatInSeconds()); ezLog::Info("Time to build dependencies: {0}s", (checkDependendtFilesEnd - parseHeaderFilesEnd).AsFloatInSeconds()); ezLog::Info("Time to write out results: {0}s", (writeOutResultsEnd - checkDependendtFilesEnd).AsFloatInSeconds()); ezLog::Info("Total time taken: {0}s", (writeOutResultsEnd - start).AsFloatInSeconds()); QuitApplication(); } }; void ParseHeaderFileTask::Execute() { ezDependencyAnalysisApp::FindDependencies(m_sPath, m_Dependencies); } void CollectDependenciesTask::Execute() { EZ_ASSERT_DEBUG(m_pContext != nullptr && m_pApp != nullptr, "Task was not initialized properly"); auto& compilationUnitDependencies = m_pContext->m_dependencies; ezDynamicArray<ezString> filesToCheck; if (m_LeftoverFiles.GetCount() > 0) { filesToCheck.Swap(m_LeftoverFiles); m_ParseTasks.Clear(); } else { for (auto& include : compilationUnitDependencies) { filesToCheck.PushBack(include); } } ezHashSet<ezString> seenFiles; while (!filesToCheck.IsEmpty()) { auto include = std::move(filesToCheck.PeekBack()); filesToCheck.PopBack(); if (seenFiles.Insert(include)) { continue; } bool alreadyExisted = false; DependencyListType* headerDependencies = nullptr; if (!m_pApp->m_HeaderDependencies.TryGetValue(include.GetView(), headerDependencies)) { { EZ_LOCK(m_pApp->m_HeaderParsingScheduledMutex); if (!m_pApp->m_HeaderParsingScheduled.Insert(include.GetView())) { if (m_ParseTasks.GetCount() == 0) { m_ParseTaskGroup = ezTaskSystem::CreateTaskGroup(ezTaskPriority::EarlyThisFrame, ezMakeDelegate(&CollectDependenciesTask::OnParsingFinished, this)); } ezSharedPtr<ParseHeaderFileTask> task = EZ_DEFAULT_NEW(ParseHeaderFileTask); task->Initialize(include); ezTaskSystem::AddTaskToGroup(m_ParseTaskGroup, task); m_ParseTasks.PushBack(std::move(task)); } } } else { ezStringBuilder tmpPath; for (auto& dependency : *headerDependencies) { if (m_pApp->FindIncludeFile(dependency.GetView(), *m_pContext, tmpPath).Succeeded()) { if (!compilationUnitDependencies.Contains(tmpPath.GetView())) { compilationUnitDependencies.Insert(tmpPath); filesToCheck.PushBack(tmpPath); } } else { if (g_bVerbose) { ezLog::Warning("Failed to find dependency '{0}' of compilation unit '{1}'", dependency, m_sPath); } } } } } } void CollectDependenciesTask::ScheduleRemainingWork() { if (m_ParseTasks.GetCount() > 0) { ezTaskSystem::StartTaskGroup(m_ParseTaskGroup); } } void CollectDependenciesTask::OnParsingFinished(ezTaskGroupID taskGroupId) { EZ_LOCK(m_pApp->m_HeaderDependenciesMutex); for (auto& task : m_ParseTasks) { bool alreadyExisted = false; auto& deps = m_pApp->m_HeaderDependencies.FindOrAdd(task->GetPath(), &alreadyExisted); if (!alreadyExisted) { deps = task->MoveOutDependencies(); } } } EZ_APPLICATION_ENTRY_POINT(ezDependencyAnalysisApp);