Files
o3de/Code/Editor/Util/PathUtil.cpp
T
lumberyard-employee-dm f648cb1fd8 Update the FileIO Aliases (#4186)
* Update the FileIOAlias naming to make the cache, project root and engine
root paths more clear

The alias of `@root@`, `@assets@`, and `@projectplatformcache@` has been
collapsed to `@projectproductassets@`

The alias of `@devroot@` and `@engroot@` has been collapsed to
`@engroot@`

The alias of `@devassets@` and `@projectroot@` has been collapsed to
`@projectroot@`

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

* Updated use of devassets and devroot properties in python

Those properties now use projectroot and engroot

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

* Updating the alias @engroot@ alias path comment in each platform specific LocalFileIO_*.cpp file

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

* Removed hardcoded size of 9 for the product asset alias.

The ResolvePath function now just appends the @projectproductassets@
alias with the input path

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

* Remove duplicate @projectproductassets@ check in ProcessFileTreeRequest

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

* Fix for typos in Hydra python test

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

* Updated LocalFileIO::Copy call on Windows to use the Unicode aware CopyFileW API

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

* Updated the AWSMetreicsGemAllocatorFixture to properly suppress asset
cache write errors for Test file creation.

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

* Removed unneeded call to set the @projectproductasstes@ alias at the bottom of the AssetSeedManagerTest SetUp

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

* Added a deprecated alias map to the FileIO System

When a deprecated alias is accessed, the FileIO System logs an AZ_Error and indicates the alias that should be used

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

* Updated python test scripts to use the projectroot binding

Retrieving the AutomatedTesting project path based on "<devroot>/AutomatedTesting" has been removed.

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

* Updated references to devroot and devgame within the codebase

The GetAbsoluteDevGameFolderPath functions has been replaced with direct call to AZ::Utils::GetProjectPath
The GetAbsoluteDevRootFolderPath functions has been replaced with direct calls to AZ::Utils::GetEnginePath

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

* Updated <engroot>/AutomatedTesting references to projectroot


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

* Replaced references that assumes the project path is <engroot>/AutomatedTesting with <projectroot> in the AutomatedTesting python test

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

* Correct casing in emfxworkspace file


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

* Removed newly added AppendPathParts function
Removed the Path constructors which accepts a PathIterable instance

The PathIterable isn't safe to return to a user of the Path class as it might be referencing temporary paths supplied via PathView arguments

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

* Fixed unused parameter warning

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

* Undid change to the LexicallyProximate function to set the path result to the base path.

It needs to return the *this path if the pathResult is empty

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

* Moved the LocalFileIO ConvertToAbsolutePath implementations to AZ::Utils

Fixed the ConvertToAbsolutePath implementation for Unix platforms to use a buffer that is size PATH_MAX(4096 on all our supported Unix platforms).
Because the buffer before was AZ::IO::MaxPathLength which as a size of 1024, this was resulting in the Unix `realpath` function smashing the stack when it converts a path to an absolute path that is longer than 1024 characters

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

* Updated the EditorCore.Tests to attach the AZ Environment to the EditorCore shared library that is statically loaded on launch.

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

* Fixed for DeprecatedAlaisesKeyVisitor Visit function causing the non string_view overloads being hidden causing a hidden base class function warning

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

* Changed the AWSMetricsGemMock to use a temporary for writing test assets

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

* Updated the LocalFileIO::ResolvePath function to use HasRootPath to determine if a path starts with a root path such as "D:", "D:/" or "/"

IsAbsolute was not the corect check as the path of "D:" is a relative
path.
To be absolute according to the Windows the path must have a root
directory. i.e "D:/" or "D:\"

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

* Removed absolute path comment from LocalFile_UnixLike.cpp and LocalFile_Android.cpp FindFiles implementations
Updated the ConvertToAlias to supply an AZ::IO::FixedMaxPath

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

* Replaced usage of the @projectproductassets@ alias with @engroot@ when referring to the LmbrCentral source folder in the CopyDependencyBuilderTest and the SeedBuilderTests

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

* Updated the ScriptCanvas Upgrade Tool to output backed up files to the
Project User Path instead of the engine root

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

* Fixed whitespacing issues in Application.cpp

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

* Remove unnecessary creation of a FixedMaxPath in the UpgradeTool.cpp

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

* Modified testSeedFolder variable in the SeedBuilderTests to use the
@engroot@ alias instead of @projectproductassets@/.. alias when
referring to the LmbrCentral Gem source path

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

* Updated references to the Project Asset Cache in the PythonTests.

Those tests no longer use the logic `azlmbr.paths.projectroot / "Cache" / "pc"` to retrieve a path to the cache root but instead the `azlmbr.paths.projectproductassets` constant

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

* Fixed the FileIO Deprecated Alias test on Windows

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

* Removing @projectsourceassets@ alias, as it is only used once.

Updated the PhysX EditorSystemComponent.cpp to query the ProjectPath
form the SettingsRegistry.

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

* Replaced @projectproductassets@ alias with @products@

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

* Rollback changes to the PhysX EditorSystemComponent.cpp

The changes to use the ProjectPath from the SettingsRegistry has been implemented in PR #4497

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-10-05 15:12:53 -05:00

418 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 <AzFramework/API/ApplicationAPI.h>
#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 char* engineRoot;
EBUS_EVENT_RESULT(engineRoot, AzFramework::ApplicationRequests::Bus, GetEngineRoot);
return QString(engineRoot);
}
//////////////////////////////////////////////////////////////////////////
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('/'));
}
}