123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "MissingDependencyScanner.h"
- AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option")
- AZ_POP_DISABLE_WARNING
- #include "LineByLineDependencyScanner.h"
- #include "PotentialDependencies.h"
- #include "native/AssetDatabase/AssetDatabase.h"
- #include "native/assetprocessor.h"
- #include <AzCore/Component/TickBus.h>
- #include <AzCore/IO/FileIO.h>
- #include <AzCore/IO/Path/Path.h>
- #include <AzCore/std/smart_ptr/make_shared.h>
- #include <AzCore/std/smart_ptr/shared_ptr.h>
- #include <AzCore/std/string/wildcard.h>
- #include <AzCore/Utils/Utils.h>
- #include <AzCore/XML/rapidxml.h>
- #include <AzFramework/API/ApplicationAPI.h>
- #include <AzFramework/FileTag/FileTag.h>
- #include <AzFramework/FileTag/FileTagBus.h>
- namespace AssetProcessor
- {
- constexpr auto EngineFolder = AZ::IO::FixedMaxPath("Assets") / "Engine";
- AZStd::string GetXMLDependenciesFile(const AZStd::string& fullPath, const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AZStd::string& tokenName)
- {
- AZ::IO::Path xmlDependenciesFileFullPath;
- tokenName = EngineFolder.String();
- for (const AzFramework::GemInfo& gemElement : gemInfoList)
- {
- for (const AZ::IO::Path& absoluteSourcePath : gemElement.m_absoluteSourcePaths)
- {
- if (AZ::IO::PathView(fullPath).IsRelativeTo(absoluteSourcePath))
- {
- xmlDependenciesFileFullPath = absoluteSourcePath;
- xmlDependenciesFileFullPath /= AzFramework::GemInfo::GetGemAssetFolder();
- xmlDependenciesFileFullPath /= AZStd::string::format("%s_Dependencies.xml", gemElement.m_gemName.c_str());;
- if (AZ::IO::FileIOBase::GetInstance()->Exists(xmlDependenciesFileFullPath.c_str()))
- {
- tokenName = gemElement.m_gemName;
- return xmlDependenciesFileFullPath.Native();
- }
- }
- }
- }
- // if we are here than either the %gemName%_Dependencies.xml file does not exists or the user inputted path is not inside a gems folder,
- // in both the cases we will return the engine dependencies file
- xmlDependenciesFileFullPath = AZ::Utils::GetEnginePath();
- xmlDependenciesFileFullPath /= EngineFolder;
- xmlDependenciesFileFullPath /= "Engine_Dependencies.xml";
- return xmlDependenciesFileFullPath.Native();
- }
- const int MissingDependencyScanner::DefaultMaxScanIteration = 800;
- class MissingDependency
- {
- public:
- MissingDependency(const AZ::Data::AssetId& assetId, const PotentialDependencyMetaData& metaData) :
- m_assetId(assetId),
- m_metaData(metaData)
- {
- }
- // Allows MissingDependency to be in a sorted container, which stabilizes log output.
- bool operator<(const MissingDependency& rhs) const
- {
- return m_assetId < rhs.m_assetId;
- }
- AZ::Data::AssetId m_assetId;
- PotentialDependencyMetaData m_metaData;
- };
- MissingDependencyScanner::MissingDependencyScanner()
- {
- m_defaultScanner = AZStd::make_shared<LineByLineDependencyScanner>();
- ApplicationManagerNotifications::Bus::Handler::BusConnect();
- MissingDependencyScannerRequestBus::Handler::BusConnect();
- }
- MissingDependencyScanner::~MissingDependencyScanner()
- {
- MissingDependencyScannerRequestBus::Handler::BusDisconnect();
- ApplicationManagerNotifications::Bus::Handler::BusDisconnect();
- }
- void MissingDependencyScanner::ApplicationShutdownRequested()
- {
- // Do not add any new functions to the SystemTickBus queue
- m_shutdownRequested = true;
- // Finish up previously queued work
- AZ::SystemTickBus::ExecuteQueuedEvents();
- }
- void MissingDependencyScanner::ScanFile(const AZStd::string& fullPath, int maxScanIteration, AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection, const AZStd::string& dependencyTokenName, bool queueDbCommandsOnMainThread, scanFileCallback callback)
- {
- AZ::s64 productPK = -1;
- AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencies = {};
- ScanFile(fullPath, maxScanIteration, productPK, dependencies, databaseConnection, dependencyTokenName, ScannerMatchType::ExtensionOnlyFirstMatch, nullptr, queueDbCommandsOnMainThread, callback);
- }
- void MissingDependencyScanner::ScanFile(
- const AZStd::string& fullPath,
- int maxScanIteration,
- AZ::s64 productPK,
- const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
- AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
- bool queueDbCommandsOnMainThread,
- scanFileCallback callback)
- {
- ScanFile(fullPath, maxScanIteration, productPK, dependencies, databaseConnection, "", ScannerMatchType::ExtensionOnlyFirstMatch, nullptr, queueDbCommandsOnMainThread, callback);
- }
- void MissingDependencyScanner::ScanFile(
- const AZStd::string& fullPath,
- int maxScanIteration,
- AZ::s64 productPK,
- const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
- AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
- AZStd::string dependencyTokenName,
- ScannerMatchType matchType,
- AZ::Crc32* forceScanner,
- bool queueDbCommandsOnMainThread,
- scanFileCallback callback)
- {
- using namespace AzFramework::FileTag;
- AZ_Printf(AssetProcessor::ConsoleChannel, "Scanning for missing dependencies:\t%s\n", fullPath.c_str());
- AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
- if (productPK != -1)
- {
- AZStd::vector<AZStd::vector<AZStd::string>> excludedTagsList = {
- {
- FileTags[static_cast<unsigned int>(FileTagsIndex::EditorOnly)]
- },
- {
- FileTags[static_cast<unsigned int>(FileTagsIndex::Shader)]
- } };
- databaseConnection->GetSourceByProductID(productPK, sourceEntry);
- for (const AZStd::vector<AZStd::string>& tags : excludedTagsList)
- {
- bool shouldIgnore = false;
- QueryFileTagsEventBus::EventResult(shouldIgnore, FileTagType::Exclude,
- &QueryFileTagsEventBus::Events::Match, fullPath.c_str(), tags);
- if (shouldIgnore)
- {
- // Record that this file was ignored in the database, so the asset tab can display this information.
- AZStd::string ignoredByTagText("File matches EditorOnly or Shader tag, ignoring for missing dependencies search.");
- AZ_Printf(AssetProcessor::ConsoleChannel, "\t%s\n", ignoredByTagText.c_str());
- SetDependencyScanResultStatus(
- ignoredByTagText,
- productPK,
- sourceEntry.m_analysisFingerprint,
- databaseConnection,
- queueDbCommandsOnMainThread,
- callback);
- return;
- }
- }
- }
- else
- {
- // if we are here than it implies that this file is not an asset
- AZStd::vector<AZStd::string> tags{
- FileTags[static_cast<unsigned int>(FileTagsIndex::Ignore)],
- FileTags[static_cast<unsigned int>(FileTagsIndex::ProductDependency)] };
- bool shouldIgnore = false;
- QueryFileTagsEventBus::EventResult(shouldIgnore, FileTagType::Exclude, &QueryFileTagsEventBus::Events::Match, fullPath.c_str(), tags);
- if (shouldIgnore)
- {
- AZ_Printf(AssetProcessor::ConsoleChannel, "File ( %s ) will be skipped by the missing dependency scanner.\n", fullPath.c_str());
- return;
- }
- }
- AZ::IO::FileIOStream fileStream;
- if (!fileStream.Open(fullPath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary))
- {
- AZ_Error(AssetProcessor::ConsoleChannel, false, "File at path %s could not be opened.", fullPath.c_str());
- // Record that this file was ignored in the database, so the asset tab can display this information.
- SetDependencyScanResultStatus(
- "The file could not be opened.",
- productPK,
- sourceEntry.m_analysisFingerprint,
- databaseConnection,
- queueDbCommandsOnMainThread,
- callback);
- return;
- }
- PotentialDependencies potentialDependencies;
- bool scanSuccessful = RunScan(fullPath, maxScanIteration, fileStream, potentialDependencies, matchType, forceScanner);
- fileStream.Close();
- if (!scanSuccessful)
- {
- // RunScan will report an error on what caused the scan to fail.
- SetDependencyScanResultStatus(
- "An error occured, see log for details.",
- productPK,
- sourceEntry.m_analysisFingerprint,
- databaseConnection,
- queueDbCommandsOnMainThread,
- callback);
- return;
- }
- MissingDependencies missingDependencies;
- PopulateMissingDependencies(productPK, databaseConnection, dependencies, missingDependencies, potentialDependencies);
- if (queueDbCommandsOnMainThread && !m_shutdownRequested)
- {
- AZ::SystemTickBus::QueueFunction([=]()
- {
- ReportMissingDependencies(productPK, databaseConnection, dependencyTokenName, missingDependencies, callback);
- });
- }
- else
- {
- ReportMissingDependencies(productPK, databaseConnection, dependencyTokenName, missingDependencies, callback);
- }
- }
- void MissingDependencyScanner::SetDependencyScanResultStatus(
- AZStd::string status,
- AZ::s64 productPK,
- const AZStd::string& analysisFingerprint,
- AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
- bool queueDbCommandsOnMainThread,
- scanFileCallback callback)
- {
- QDateTime currentTime = QDateTime::currentDateTime();
- auto finalizeMissingDependency = [=]() {
- AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry missingDependencyEntry(
- productPK,
- /*Scanner*/ "",
- /*Scanner Version*/ "",
- analysisFingerprint,
- AZ::Uuid::CreateNull(),
- /*Product ID*/ 0,
- status,
- currentTime.toString().toUtf8().constData(),
- currentTime.toSecsSinceEpoch());
- databaseConnection->SetMissingProductDependency(missingDependencyEntry);
- callback(missingDependencyEntry.m_missingDependencyString);
- };
- if (queueDbCommandsOnMainThread && !m_shutdownRequested)
- {
- AZ::SystemTickBus::QueueFunction(finalizeMissingDependency);
- }
- else
- {
- finalizeMissingDependency();
- }
- }
- void MissingDependencyScanner::RegisterSpecializedScanner(AZStd::shared_ptr<SpecializedDependencyScanner> scanner)
- {
- m_specializedScanners.insert(AZStd::pair<AZ::Crc32, AZStd::shared_ptr<SpecializedDependencyScanner>>(scanner->GetScannerCRC(), scanner));
- }
- bool MissingDependencyScanner::RunScan(
- const AZStd::string& fullPath,
- int maxScanIteration,
- AZ::IO::GenericStream& fileStream,
- PotentialDependencies& potentialDependencies,
- ScannerMatchType matchType,
- AZ::Crc32* forceScanner)
- {
- // If a scanner is given to specifically use, then use that scanner and only that scanner.
- if (forceScanner)
- {
- AZ_Printf(
- AssetProcessor::ConsoleChannel,
- "\tForcing scanner with CRC %d\n",
- *forceScanner);
- DependencyScannerMap::iterator scannerToUse = m_specializedScanners.find(*forceScanner);
- if (scannerToUse != m_specializedScanners.end())
- {
- scannerToUse->second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
- return true;
- }
- else
- {
- AZ_Error(AssetProcessor::ConsoleChannel, false, "Attempted to force dependency scan using CRC %d, which is not registered.",
- *forceScanner);
- return false;
- }
- }
- // Check if a specialized scanner should be used, based on the given scanner matching type rule.
- for (const AZStd::pair<AZ::Crc32, AZStd::shared_ptr<SpecializedDependencyScanner>>&
- scanner : m_specializedScanners)
- {
- switch (matchType)
- {
- case ScannerMatchType::ExtensionOnlyFirstMatch:
- if (scanner.second->DoesScannerMatchFileExtension(fullPath))
- {
- return scanner.second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
- }
- break;
- case ScannerMatchType::FileContentsFirstMatch:
- if (scanner.second->DoesScannerMatchFileData(fileStream))
- {
- return scanner.second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
- }
- break;
- case ScannerMatchType::Deep:
- // A deep scan has every matching scanner scan the file, and uses the default scan.
- if (scanner.second->DoesScannerMatchFileData(fileStream))
- {
- scanner.second->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
- }
- break;
- default:
- AZ_Error(AssetProcessor::ConsoleChannel, false, "Scan match type %d is not available.", matchType);
- break;
- };
- }
- // No specialized scanner was found (or a deep scan is being performed), so use the default scanner.
- return m_defaultScanner->ScanFileForPotentialDependencies(fileStream, potentialDependencies, maxScanIteration);
- }
- void MissingDependencyScanner::PopulateMissingDependencies(
- AZ::s64 productPK,
- AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
- const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencies,
- MissingDependencies& missingDependencies,
- const PotentialDependencies& potentialDependencies)
- {
- // If a file references itself, don't report it.
- AzToolsFramework::AssetDatabase::SourceDatabaseEntry fileWithPotentialMissingDependencies;
- databaseConnection->GetSourceByProductID(productPK, fileWithPotentialMissingDependencies);
- AZStd::map<AZ::Uuid, PotentialDependencyMetaData> uuids(potentialDependencies.m_uuids);
- AZStd::map<AZ::Data::AssetId, PotentialDependencyMetaData> assetIds(potentialDependencies.m_assetIds);
- // Check if any products exist for the given job, and those products have a sub ID that matches
- // the expected sub ID.
- AzToolsFramework::AssetDatabase::ProductDatabaseEntry productWithPotentialMissingDependencies;
- databaseConnection->GetProductByProductID(productPK, productWithPotentialMissingDependencies);
- QString scannedProductPath( productWithPotentialMissingDependencies.m_productName.c_str() );
- auto lastSeparatorIndex = scannedProductPath.lastIndexOf(AZ_CORRECT_DATABASE_SEPARATOR_STRING);
- scannedProductPath = scannedProductPath.remove(lastSeparatorIndex + 1, scannedProductPath.length());
- // Check the existing product dependency list for the file that is being scanned, remove
- // any potential UUIDs that match dependencies already being emitted.
- for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
- existingDependency : dependencies)
- {
- AZStd::map<AZ::Uuid, PotentialDependencyMetaData>::iterator matchingDependency =
- uuids.find(existingDependency.m_dependencySourceGuid);
- if (matchingDependency != uuids.end())
- {
- uuids.erase(matchingDependency);
- }
- }
- // Remove all UUIDs that don't match an asset in the database.
- for (AZStd::map<AZ::Uuid, PotentialDependencyMetaData>::iterator uuidIter = uuids.begin();
- uuidIter != uuids.end();
- ++uuidIter)
- {
- if (fileWithPotentialMissingDependencies.m_sourceGuid == uuidIter->first)
- {
- // This product references itself, or the source it comes from. Don't report it as a missing dependency.
- continue;
- }
- AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
- if (!databaseConnection->GetSourceBySourceGuid(uuidIter->first, sourceEntry))
- {
- // The UUID isn't in the asset database, don't add it to the list of missing dependencies.
- continue;
- }
- AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer jobs;
- if (!databaseConnection->GetJobsBySourceID(sourceEntry.m_sourceID, jobs))
- {
- // No jobs existed for that source asset, so there are no products for this asset.
- // With no products, there is no way there can be a missing product dependency.
- continue;
- }
- // The dependency only referenced the source UUID, so add all products as missing dependencies.
- for (const AzToolsFramework::AssetDatabase::JobDatabaseEntry& job : jobs)
- {
- AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
- if (!databaseConnection->GetProductsByJobID(job.m_jobID, products))
- {
- continue;
- }
- for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
- {
- // This match was for a UUID with no product ID, so add all products as missing dependencies.
- MissingDependency missingDependency(
- AZ::Data::AssetId(uuidIter->first, product.m_subID),
- uuidIter->second);
- missingDependencies.insert(missingDependency);
- }
- }
- }
- // Validate the asset ID list, removing anything that is already a dependency, or does not exist in the asset database.
- for (AZStd::map<AZ::Data::AssetId, PotentialDependencyMetaData>::iterator assetIdIter = assetIds.begin();
- assetIdIter != assetIds.end();
- ++assetIdIter)
- {
- bool foundUUID = false;
- // Strip out all existing, matching dependencies
- for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
- existingDependency : dependencies)
- {
- if (existingDependency.m_dependencySourceGuid == assetIdIter->first.m_guid &&
- existingDependency.m_dependencySubID == assetIdIter->first.m_subId)
- {
- foundUUID = true;
- break;
- }
- }
- // There is already a dependency with this UUID, so it's not a missing dependency.
- if (foundUUID)
- {
- continue;
- }
- AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
- if (!databaseConnection->GetSourceBySourceGuid(assetIdIter->first.m_guid, sourceEntry))
- {
- // The UUID isn't in the asset database. Don't report it as a missing dependency
- // because UUIDs are used for tracking many things that are not assets.
- continue;
- }
- AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer jobs;
- if (!databaseConnection->GetJobsBySourceID(sourceEntry.m_sourceID, jobs))
- {
- // No jobs existed for that source asset, so there are no products for this asset.
- // With no products, there is no way there can be a missing product dependency.
- continue;
- }
- bool isProductOfFileWithPotentialMissingDependencies = fileWithPotentialMissingDependencies.m_sourceGuid == assetIdIter->first.m_guid;
- bool foundMatchingProduct = false;
- for (const AzToolsFramework::AssetDatabase::JobDatabaseEntry& job : jobs)
- {
- AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
- if (!databaseConnection->GetProductsByJobID(job.m_jobID, products))
- {
- continue;
- }
- for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
- {
- if (product.m_subID == assetIdIter->first.m_subId)
- {
- // This product references itself. Don't report it as a missing dependency.
- // If the product references a different product of the same source and that isn't
- // a dependency, then do report that.
- // We have to check against more than the productPK to catch identical products across multiple
- // platforms.
- if (productPK == product.m_productID ||
- (isProductOfFileWithPotentialMissingDependencies && productWithPotentialMissingDependencies.m_subID == product.m_subID))
- {
- continue;
- }
- MissingDependency missingDependency(
- assetIdIter->first,
- assetIdIter->second);
- missingDependencies.insert(missingDependency);
- foundMatchingProduct = true;
- break;
- }
- }
- if (foundMatchingProduct)
- {
- break;
- }
- }
- }
- for (const PotentialDependencyMetaData& path : potentialDependencies.m_paths)
- {
- AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer searchSources;
- QString searchName(path.m_sourceString.c_str());
- // The paths in the file may have had slashes in either direction, or double slashes.
- searchName.replace(AZ_WRONG_DATABASE_SEPARATOR_STRING, AZ_CORRECT_DATABASE_SEPARATOR_STRING);
- searchName.replace(AZ_DOUBLE_CORRECT_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR_STRING);
-
- if (databaseConnection->GetSourcesBySourceName(searchName, searchSources))
- {
- // A source matched the path, look up products and add them as resolved path dependencies.
- for (const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source : searchSources)
- {
- if (fileWithPotentialMissingDependencies.m_sourceGuid == source.m_sourceGuid)
- {
- // This product references itself, or the source it comes from. Don't report it as a missing dependency.
- continue;
- }
- bool dependencyExistsForSource = false;
- for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
- existingDependency : dependencies)
- {
- if (existingDependency.m_dependencySourceGuid == source.m_sourceGuid)
- {
- dependencyExistsForSource = true;
- break;
- }
- }
- if (dependencyExistsForSource)
- {
- continue;
- }
- AzToolsFramework::AssetDatabase::JobDatabaseEntryContainer jobs;
- if (!databaseConnection->GetJobsBySourceID(source.m_sourceID, jobs))
- {
- // No jobs exist for this source, which means there is no matching product dependency.
- continue;
- }
- for (const AzToolsFramework::AssetDatabase::JobDatabaseEntry& job : jobs)
- {
- AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
- if (!databaseConnection->GetProductsByJobID(job.m_jobID, products))
- {
- // No products, no product dependencies.
- continue;
- }
- for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
- {
- MissingDependency missingDependency(
- AZ::Data::AssetId(source.m_sourceGuid, product.m_subID),
- path);
- missingDependencies.insert(missingDependency);
- }
- }
- }
- }
- else
- {
- // Product paths in the asset database include the platform and additional pathing information that
- // makes this check more complex than the source path check.
- // Examples:
- // pc/usersettings.xml
- // pc/ProjectName/file.xml
- // Taking all results from this EndsWith check can lead to an over-emission of potential missing dependencies.
- // For example, if a file has a comment like "Something about .dds files", then EndsWith would return
- // every single dds file in the database.
- AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
- if (!databaseConnection->GetProductsLikeProductName(searchName, AssetDatabaseConnection::LikeType::EndsWith, products))
- {
- continue;
- }
- for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
- {
- if (productPK == product.m_productID)
- {
- // Don't report if a file has a reference to itself.
- continue;
- }
- // Cull the platform from the product path to perform a more confident comparison against the given path.
- QString culledProductPath = QString(product.m_productName.c_str());
- // If this appears to be a valid path to a product relative to the product being checked
- if (culledProductPath.compare(scannedProductPath.append(searchName), Qt::CaseInsensitive) != 0)
- {
- culledProductPath = culledProductPath.remove(0, culledProductPath.indexOf(AZ_CORRECT_DATABASE_SEPARATOR_STRING) + 1);
- // This first check will catch paths that include the project name, as well as references to assets that include a scan folder in the path.
- if (culledProductPath.compare(searchName, Qt::CaseInsensitive) != 0)
- {
- int nextFolderIndex = culledProductPath.indexOf(AZ_CORRECT_DATABASE_SEPARATOR_STRING);
- if (nextFolderIndex == -1)
- {
- continue;
- }
- // Perform a second check with the scan folder removed. Many asset references are relevant to scan folder roots.
- // For example, a material may have a relative path reference to a texture as "textures/SomeTexture.dds".
- // This relative path resolves in many systems based on scan folder root, so if this file is in "platform/project/textures/SomeTexture.dds",
- // this check is intended to find that reference.
- culledProductPath = culledProductPath.remove(0, nextFolderIndex + 1);
- if (culledProductPath.compare(searchName, Qt::CaseInsensitive) != 0)
- {
- continue;
- }
- }
- }
- AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer productSources;
- if (!databaseConnection->GetSourcesByProductName(QString(product.m_productName.c_str()), productSources))
- {
- AZ_Error(
- AssetProcessor::ConsoleChannel,
- false,
- "Product %s does not have a matching source. Your database may be corrupted.", product.m_productName.c_str());
- continue;
- }
- for (const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source : productSources)
- {
- bool dependencyExistsForProduct = false;
- for (const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry&
- existingDependency : dependencies)
- {
- if (existingDependency.m_dependencySourceGuid == source.m_sourceGuid &&
- existingDependency.m_dependencySubID == product.m_subID)
- {
- dependencyExistsForProduct = true;
- break;
- }
- }
- if (!dependencyExistsForProduct)
- {
- AZ::Data::AssetId assetId(source.m_sourceGuid, product.m_subID);
- MissingDependency missingDependency(
- AZ::Data::AssetId(source.m_sourceGuid, product.m_subID),
- path);
- missingDependencies.insert(missingDependency);
- }
- }
- }
- }
- }
- }
- void MissingDependencyScanner::ReportMissingDependencies(
- AZ::s64 productPK,
- AZStd::shared_ptr<AssetDatabaseConnection> databaseConnection,
- const AZStd::string& dependencyTokenName,
- const MissingDependencies& missingDependencies,
- scanFileCallback callback)
- {
- using namespace AzFramework::FileTag;
- AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
- databaseConnection->GetSourceByProductID(productPK, sourceEntry);
- AZStd::vector<AZStd::string> tags{
- FileTags[static_cast<unsigned int>(FileTagsIndex::Ignore)],
- FileTags[static_cast<unsigned int>(FileTagsIndex::ProductDependency)] };
- QDateTime currentTime = QDateTime::currentDateTime();
- // If there were no missing dependencies, add a row to the table so we know it was scanned.
- if (productPK != -1 && missingDependencies.empty())
- {
- SetDependencyScanResultStatus(
- "No missing dependencies found",
- productPK,
- sourceEntry.m_analysisFingerprint,
- databaseConnection,
- /*queueDbCommandsOnMainThread*/ false, // ReportMissingDependencies was already queued to run on the main thread.
- callback);
- return;
- }
- for (const MissingDependency& missingDependency : missingDependencies)
- {
- bool shouldIgnore = false;
- QueryFileTagsEventBus::EventResult(shouldIgnore, FileTagType::Exclude,
- &QueryFileTagsEventBus::Events::Match, missingDependency.m_metaData.m_sourceString.c_str(), tags);
- if (!shouldIgnore && !dependencyTokenName.empty())
- {
- // if one of the rules in the xml dependency file match then skip the missing dependency
- auto rulesFound = m_dependenciesRulesMap.find(dependencyTokenName);
- if (rulesFound != m_dependenciesRulesMap.end())
- {
- for (const auto& rule : rulesFound->second)
- {
- if (AZStd::wildcard_match(rule, missingDependency.m_metaData.m_sourceString))
- {
- shouldIgnore = true;
- break;
- }
- }
- }
- }
- if (!shouldIgnore)
- {
- AZStd::string assetIdStr = missingDependency.m_assetId.ToString<AZStd::string>();
- AZ_Printf(
- AssetProcessor::ConsoleChannel,
- "\t\tMissing dependency: String \"%s\" matches asset: %s\n",
- missingDependency.m_metaData.m_sourceString.c_str(),
- assetIdStr.c_str());
- if (productPK != -1)
- {
- AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry missingDependencyEntry(
- productPK,
- missingDependency.m_metaData.m_scanner->GetName(),
- missingDependency.m_metaData.m_scanner->GetVersion(),
- sourceEntry.m_analysisFingerprint,
- missingDependency.m_assetId.m_guid,
- missingDependency.m_assetId.m_subId,
- missingDependency.m_metaData.m_sourceString,
- currentTime.toString().toUtf8().constData(),
- currentTime.toSecsSinceEpoch());
- databaseConnection->SetMissingProductDependency(missingDependencyEntry);
- }
- callback(missingDependency.m_metaData.m_sourceString);
- }
- }
- }
- bool MissingDependencyScanner::PopulateRulesForScanFolder(const AZStd::string& scanFolderPath, const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AZStd::string& dependencyTokenName)
- {
- AZStd::string xmlDependenciesFullFilePath = GetXMLDependenciesFile(scanFolderPath, gemInfoList, dependencyTokenName);
- if (xmlDependenciesFullFilePath.empty())
- {
- AZ_Printf(AssetProcessor::ConsoleChannel, "Unable to find xml dependency file for the directory scan %s\n", scanFolderPath.c_str());
- }
- auto found = m_dependenciesRulesMap.find(dependencyTokenName);
- if (found != m_dependenciesRulesMap.end())
- {
- // this imply that we have already parsed this file and populated the rules,
- // therefore we can exit early.
- return true;
- }
- if (!AZ::IO::FileIOBase::GetInstance()->Exists(xmlDependenciesFullFilePath.c_str()))
- {
- AZ_Printf(AssetProcessor::ConsoleChannel, "Unable to find xml dependency file (%s). \n", xmlDependenciesFullFilePath.c_str());
- return false;
- }
- AZ::IO::FileIOStream fileStream;
- AZStd::vector<AZStd::string> dependenciesRuleList;
- if (fileStream.Open(xmlDependenciesFullFilePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary))
- {
- if (!fileStream.CanRead())
- {
- return false;
- }
- AZ::IO::SizeType length = fileStream.GetLength();
- if (length == 0)
- {
- return false;
- }
- AZStd::vector<char> charBuffer;
- charBuffer.resize_no_construct(length + 1);
- fileStream.Read(length, charBuffer.data());
- charBuffer.back() = 0;
- AZ::rapidxml::xml_document<char> xmlDoc;
- xmlDoc.parse<AZ::rapidxml::parse_no_data_nodes>(charBuffer.data());
- auto engineDependenciesNode = xmlDoc.first_node("EngineDependencies");
- if (!engineDependenciesNode)
- {
- return false;
- }
- auto dependencyNode = engineDependenciesNode->first_node("Dependency");
- while (dependencyNode)
- {
- auto pathAttr = dependencyNode->first_attribute("path");
- if (pathAttr)
- {
- dependenciesRuleList.emplace_back(AZStd::string(pathAttr->value()));
- }
- dependencyNode = dependencyNode->next_sibling();
- }
- m_dependenciesRulesMap[dependencyTokenName] = dependenciesRuleList;
- return true;
- }
- return false;
- }
- }
|