Files
o3de/Code/Editor/Util/PathUtil.cpp
T
lumberyard-employee-dm 5fc4551ac0 [LYN-8041] Enable relocation of the Project Game Release Layout (#5380)
* Enable relocation of the Project Game Release Layout

Relocating the Project Game Release Layout to another directory on the file system failed due to the querying of the engine root failing due to the ComponentApplication::m_engineRoot not using the project path stored in the SettingsRegisry if the engine root cannot be detected

Removed the ApplicationRequestBus GetEngineRoot function.
The ComponentApplicationRequestBus has a function of the same name that returns the same path.

Removed the deprecated GetAppRoot function.
The path it returns has no defined value. It was not the engine root or the project root.
Removed unused CFileUtil and CFileUtil_impl functions that were invoking the ApplicationREquestBus GetEngineRoot function.
On the way to update the functions it was discovered that they aren't called

Added a CalculateBranchToken overload that can populate a fixed_string to avoid heap allocations

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Protect against an empty list of artifacts to remove when generating the
engine.pak

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-11-09 12:03:52 -06:00

416 lines
14 KiB
C++

/*
* 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 "EditorDefs.h"
#include "PathUtil.h"
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h> // for ebus events
#include <AzCore/std/string/conversions.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <QRegularExpression>
namespace Path
{
//////////////////////////////////////////////////////////////////////////
void SplitPath(const QString& rstrFullPathFilename, QString& rstrDriveLetter, QString& rstrDirectory, QString& rstrFilename, QString& rstrExtension)
{
AZStd::string strFullPathString(rstrFullPathFilename.toUtf8().data());
AZStd::string strDriveLetter;
AZStd::string strDirectory;
AZStd::string strFilename;
AZStd::string strExtension;
char* szPath((char*)strFullPathString.c_str());
char* pchLastPosition(szPath);
char* pchCurrentPosition(szPath);
char* pchAuxPosition(szPath);
// Directory named filenames containing ":" are invalid, so we can assume if there is a :
// it will be the drive name.
pchCurrentPosition = strchr(pchLastPosition, ':');
if (pchCurrentPosition == nullptr)
{
rstrDriveLetter = "";
}
else
{
strDriveLetter.assign(pchLastPosition, pchCurrentPosition + 1);
pchLastPosition = pchCurrentPosition + 1;
}
pchCurrentPosition = strrchr(pchLastPosition, '\\');
pchAuxPosition = strrchr(pchLastPosition, '/');
if ((pchCurrentPosition == nullptr) && (pchAuxPosition == nullptr))
{
rstrDirectory = "";
}
else
{
// Since NULL is < valid pointer, so this will work.
if (pchAuxPosition > pchCurrentPosition)
{
pchCurrentPosition = pchAuxPosition;
}
strDirectory.assign(pchLastPosition, pchCurrentPosition + 1);
pchLastPosition = pchCurrentPosition + 1;
}
pchCurrentPosition = strrchr(pchLastPosition, '.');
if (pchCurrentPosition == nullptr)
{
rstrExtension = "";
strFilename.assign(pchLastPosition);
}
else
{
strExtension.assign(pchCurrentPosition);
strFilename.assign(pchLastPosition, pchCurrentPosition);
}
rstrDriveLetter = strDriveLetter.c_str();
rstrDirectory = strDirectory.c_str();
rstrFilename = strFilename.c_str();
rstrExtension = strExtension.c_str();
}
//////////////////////////////////////////////////////////////////////////
void GetDirectoryQueue(const QString& rstrSourceDirectory, QStringList& rcstrDirectoryTree)
{
AZStd::string strCurrentDirectoryName;
AZStd::string strSourceDirectory(rstrSourceDirectory.toUtf8().data());
const char* szSourceDirectory(strSourceDirectory.c_str());
const char* pchCurrentPosition(szSourceDirectory);
const char* pchLastPosition(szSourceDirectory);
rcstrDirectoryTree.clear();
if (strSourceDirectory.empty())
{
return;
}
// It removes as many slashes the path has in its start...
// MAYBE and just maybe we should consider paths starting with
// more than 2 slashes invalid paths...
while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
{
++pchLastPosition;
++pchCurrentPosition;
}
do
{
pchCurrentPosition = strpbrk(pchLastPosition, "\\/");
if (pchCurrentPosition == nullptr)
{
break;
}
strCurrentDirectoryName.assign(pchLastPosition, pchCurrentPosition);
pchLastPosition = pchCurrentPosition + 1;
// Again, here we are skipping as many consecutive slashes.
while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
{
++pchLastPosition;
}
rcstrDirectoryTree.push_back(strCurrentDirectoryName.c_str());
} while (true);
}
//////////////////////////////////////////////////////////////////////////
void ConvertSlashToBackSlash(QString& rstrStringToConvert)
{
rstrStringToConvert.replace('/', '\\');
rstrStringToConvert = CaselessPaths(rstrStringToConvert);
}
//////////////////////////////////////////////////////////////////////////
void ConvertBackSlashToSlash(QString& rstrStringToConvert)
{
rstrStringToConvert.replace('\\', '/');
rstrStringToConvert = CaselessPaths(rstrStringToConvert);
}
//////////////////////////////////////////////////////////////////////////
void SurroundWithQuotes(QString& rstrSurroundString)
{
QString strSurroundString(rstrSurroundString);
if (!strSurroundString.isEmpty())
{
if (strSurroundString[0] != '\"')
{
strSurroundString.insert(0, "\"");
}
if (strSurroundString[strSurroundString.size() - 1] != '\"')
{
strSurroundString.insert(strSurroundString.size(), "\"");
}
}
else
{
strSurroundString.insert(0, "\"");
strSurroundString.insert(strSurroundString.size(), "\"");
}
rstrSurroundString = strSurroundString;
}
//////////////////////////////////////////////////////////////////////////
QString GetExecutableFullPath()
{
return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
}
//////////////////////////////////////////////////////////////////////////
QString GetWindowsTempDirectory()
{
return QDir::tempPath();
}
//////////////////////////////////////////////////////////////////////////
QString GetEngineRootPath()
{
const AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath();
return QString::fromUtf8(engineRoot.c_str(), static_cast<int>(engineRoot.size()));
}
//////////////////////////////////////////////////////////////////////////
QString& ReplaceFilename(const QString& strFilepath, const QString& strFilename, QString& strOutputFilename, bool bCallCaselessPath)
{
QString strDriveLetter;
QString strDirectory;
QString strOriginalFilename;
QString strExtension;
SplitPath(strFilepath, strDriveLetter, strDirectory, strOriginalFilename, strExtension);
strOutputFilename = strDriveLetter;
strOutputFilename += strDirectory;
strOutputFilename += strFilename;
strOutputFilename += strExtension;
if (bCallCaselessPath)
{
strOutputFilename = CaselessPaths(strOutputFilename);
}
return strOutputFilename;
}
bool IsFolder(const char* pPath)
{
return AZ::IO::FileIOBase::GetInstance()->IsDirectory(pPath);
}
//////////////////////////////////////////////////////////////////////////
QString GetUserSandboxFolder()
{
return QString::fromUtf8("@user@/Sandbox/");
}
//////////////////////////////////////////////////////////////////////////
QString GetResolvedUserSandboxFolder()
{
AZ::IO::FixedMaxPath userSandboxFolderPath;
gEnv->pFileIO->ResolvePath(userSandboxFolderPath, GetUserSandboxFolder().toUtf8().constData());
return QString::fromUtf8(userSandboxFolderPath.c_str(), static_cast<int>(userSandboxFolderPath.Native().size()));
}
// internal function, you should use GetEditingGameDataFolder instead.
AZStd::string GetGameAssetsFolder()
{
AZ::IO::Path projectPath;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
}
return projectPath.Native();
}
/// Get the data folder
AZStd::string GetEditingGameDataFolder()
{
// Define here the mod name
static AZ::IO::FixedMaxPathString s_currentModName;
if (s_currentModName.empty())
{
return GetGameAssetsFolder();
}
AZStd::string str(GetGameAssetsFolder());
str += "Mods\\";
str += s_currentModName.c_str();
return str;
}
AZStd::string MakeModPathFromGamePath(const char* relGamePath)
{
return GetEditingGameDataFolder() + "\\" + relGamePath;
}
QString FullPathToLevelPath(const QString& path)
{
if (path.isEmpty())
{
return "";
}
QString relGamePath;
if (!QFileInfo(path).isRelative())
{
relGamePath = GetRelativePath(path);
}
else
{
relGamePath = path;
}
QString levelpath = GetIEditor()->GetLevelFolder();
QString str = levelpath;
str.replace('/', '\\');
levelpath = CaselessPaths(str);
// Create relative path
QString relLevelPath = QDir(levelpath).relativeFilePath(relGamePath);
if (relLevelPath.isEmpty())
{
assert(0);
return path;
}
relLevelPath.remove(QRegularExpression(QStringLiteral(R"(^[\\/.]*)")));
return relLevelPath;
}
QString Make(const QString& path, const QString& file)
{
if (gEnv->pCryPak->IsAbsPath(file.toUtf8().data()))
{
return file;
}
return CaselessPaths(AddPathSlash(path) + file);
}
QString GetRelativePath(const QString& fullPath, bool bRelativeToGameFolder /*= false*/)
{
if (fullPath.isEmpty())
{
return "";
}
bool relPathFound = false;
AZStd::string relativePath;
AZStd::string fullAssetPath(fullPath.toUtf8().data());
EBUS_EVENT_RESULT(relPathFound, AzToolsFramework::AssetSystemRequestBus, GetRelativeProductPathFromFullSourceOrProductPath, fullAssetPath, relativePath);
if (relPathFound)
{
// do not normalize this path, it will already be an appropriate asset ID.
return CaselessPaths(relativePath.c_str());
}
AZ::IO::FixedMaxPath rootPath = bRelativeToGameFolder ? AZ::Utils::GetProjectPath() : AZ::Utils::GetEnginePath();
AZ::IO::FixedMaxPath resolvedFullPath;
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(resolvedFullPath, fullPath.toUtf8().constData());
// Create relative path
return CaselessPaths(resolvedFullPath.LexicallyProximate(rootPath).MakePreferred().c_str());
}
QString GamePathToFullPath(const QString& path)
{
using namespace AzToolsFramework;
AZ_Warning("GamePathToFullPath", path.size() <= AZ::IO::MaxPathLength, "Path exceeds maximum path length of %zu", AZ::IO::MaxPathLength);
if (path.size() <= AZ::IO::MaxPathLength)
{
// first, adjust the file name for mods:
bool fullPathFound = false;
AZ::IO::Path assetFullPath;
AZ::IO::Path adjustedFilePath = path.toUtf8().constData();
AssetSystemRequestBus::BroadcastResult(fullPathFound, &AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath,
adjustedFilePath.Native(), assetFullPath.Native());
if (fullPathFound)
{
//if the bus message succeeds than normalize
return assetFullPath.LexicallyNormal().c_str();
}
// if the bus message didn't succeed, check if he path exist as a resolved path
else
{
// Not all systems have been converted to use local paths. Some editor files save XML files directly, and a full or correctly aliased path is already passed in.
// If the path passed in exists already, then return the resolved filepath
if (AZ::IO::FileIOBase::GetDirectInstance()->Exists(adjustedFilePath.c_str()))
{
AZ::IO::FixedMaxPath resolvedPath;
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(resolvedPath, adjustedFilePath);
return QString::fromUtf8(resolvedPath.c_str(), static_cast<int>(resolvedPath.Native().size()));
}
return path;
}
}
else
{
return QString{};
}
}
QString ToUnixPath(const QString& strPath, bool bCallCaselessPath)
{
QString str = strPath;
str.replace('\\', '/');
return bCallCaselessPath ? CaselessPaths(str) : str;
}
QString RemoveBackslash(QString path)
{
if (path.isEmpty())
{
return path;
}
int iLenMinus1 = path.length() - 1;
QChar cLastChar = path[iLenMinus1];
if (cLastChar == '\\' || cLastChar == '/')
{
return CaselessPaths(path.mid(0, iLenMinus1));
}
return CaselessPaths(path);
}
QString SubDirectoryCaseInsensitive(const QString& path, const QStringList& parts)
{
if (parts.isEmpty())
{
return path;
}
QStringList modifiedParts = parts;
auto currentPart = modifiedParts.takeFirst();
// case insensitive iterator
QDirIterator it(path);
while (it.hasNext())
{
it.next();
// the current part already exists, use it, case doesn't matter
auto actualName = it.fileName();
if (QString::compare(actualName, currentPart, Qt::CaseInsensitive) == 0)
{
return SubDirectoryCaseInsensitive(QDir(path).absoluteFilePath(actualName), modifiedParts);
}
}
// the current path doesn't exist yet, so just create the complete path in one rush
return QDir(path).absoluteFilePath(parts.join('/'));
}
}