Problem:
We use a common utility class (FORPPathData) to provide global access to game save paths and related directories (e.g., videos, images). The design allows the project save path to be modified at runtime, which is essential for automation tests. However, during automated test execution, the path returned by FPaths::ProjectContentDir() consistently reflects the machine where the binaries were originally compiled, rather than the current runtime environment.
Expected Behavior:
GetProjectSavePath() should return the correct project content directory for the machine where the test is running, or the overridden path set via SetProjectSavePath().
struct ARVRPATHDATA_API FORPPathData
{
GENERATED_BODY()
/* Runtime Modifiable Paths */
inline static FString ProjectSavePath = FPaths::ProjectContentDir();
...
static FString GetRoomConfJsonDirectory() { return ProjectSavePath / RoomConfJsonDirectoryName; }
Tried with removing inline and make it only static variable that can be set during runtime:
struct ARVRPATHDATA_API FORPPathData
{
GENERATED_BODY()
/* Runtime Modifiable Paths */
inline static const FString UserDataPathName = "DefaultUser";
static FString ProjectSavePath;
// Instead of storing ProjectSavePath as a static initialized value:
static FString GetProjectSavePath()
{
if (ProjectSavePath.IsEmpty())
return FPaths::ProjectContentDir() / UserDataPathName;
return ProjectSavePath;
}
static void SetProjectSavePath(const FString& NewPath)
{
ProjectSavePath = NewPath;
}
We tried to use the macro value with FILE to get the path.
static FString GetProjectSavePath()
{
if (ProjectSavePath.IsEmpty())
{
#if WITH_EDITOR
FString CurrentFileDirectory = FPaths::GetPath(__FILE__);
FString PluginDirectoryFolderName = "Plugins";
while (!CurrentFileDirectory.IsEmpty())
{
FString FolderName = FPaths::GetCleanFilename(CurrentFileDirectory);
if (FolderName.Equals(PluginDirectoryFolderName, ESearchCase::IgnoreCase))
{
CurrentFileDirectory = FPaths::GetPath(CurrentFileDirectory);
break;
}
// Get Project Directory Which is one level above Plugins
CurrentFileDirectory = FPaths::GetPath(CurrentFileDirectory);
}
return FPaths::Combine(CurrentFileDirectory, ContentDirectoryName, UserDataDirName);
#else
return FPaths::Combine(FPaths::ProjectContentDir(), UserDataDirName);
#endif
}
return ProjectSavePath;
}
We also tried to use optimization macros, but still it is happening. It only happens with the automation tests, in editor and builds are working properly.
struct ARVRPATHDATA_API FORPPathData
{
GENERATED_BODY()
/* Runtime Modifiable Paths */
inline static const FString UserDataDirName = "DefaultUser";
static FString ProjectSavePath;
/* Instead of storing ProjectSavePath as a static initialized value, we lazily initialize it the first time it is
* requested. This allows us to determine the correct path based on whether we are in the editor or in a packaged
* build.In the editor, we traverse up the directory structure to find the "Plugins" folder and then go up one
* level to get the project root. In a packaged build, we simply use the ProjectContentDir.
*
* We do this because FPaths::ProjectContentDir() for a reason we do not understand saves the path when the binary
* is compiled from the computer it was compiled in. This makes the project fail when using those binaries without
* recompiling on a different machine.
*/
UE_DISABLE_OPTIMIZATION
static FString GetProjectSavePath()
{
if (ProjectSavePath.IsEmpty())
{
return FPaths::Combine(FPaths::ProjectContentDir(), UserDataDirName);
}
return ProjectSavePath;
}
UE_ENABLE_OPTIMIZATION
static void SetProjectSavePath(const FString& NewPath)
{
ProjectSavePath = NewPath;
}