Files
FlaxEngine/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
2023-04-11 15:53:53 +02:00

445 lines
15 KiB
C++

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if PLATFORM_TOOLS_UWP
#include "UWPPlatformTools.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/UWP/UWPPlatformSettings.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Serialization/FileWriteStream.h"
#include "Editor/Utilities/EditorUtilities.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
IMPLEMENT_ENGINE_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform);
const Char* UWPPlatformTools::GetDisplayName() const
{
return TEXT("Windows Store");
}
const Char* UWPPlatformTools::GetName() const
{
return TEXT("UWP");
}
PlatformType UWPPlatformTools::GetPlatform() const
{
return PlatformType::UWP;
}
ArchitectureType UWPPlatformTools::GetArchitecture() const
{
return _arch;
}
DotNetAOTModes UWPPlatformTools::UseAOT() const
{
return DotNetAOTModes::MonoAOTDynamic;
}
bool UWPPlatformTools::OnScriptsStepDone(CookingData& data)
{
// Override Newtonsoft.Json.dll for some platforms (that don't support runtime code generation)
const String customBinPath = data.GetPlatformBinariesRoot() / TEXT("Newtonsoft.Json.dll");
const String assembliesPath = data.ManagedCodeOutputPath;
if (FileSystem::CopyFile(assembliesPath / TEXT("Newtonsoft.Json.dll"), customBinPath))
{
data.Error(TEXT("Failed to copy deploy custom assembly."));
return true;
}
FileSystem::DeleteFile(assembliesPath / TEXT("Newtonsoft.Json.pdb"));
return false;
}
bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
{
const auto platformDataPath = Globals::StartupFolder / TEXT("Source/Platforms");
const auto uwpDataPath = platformDataPath / TEXT("UWP/Binaries");
const auto gameSettings = GameSettings::Get();
const auto platformSettings = UWPPlatformSettings::Get();
StringAnsi fileTemplate;
// Copy binaries
const auto binPath = data.GetGameBinariesPath();
Array<String> files;
files.Add(binPath / TEXT("FlaxEngine.pri"));
files.Add(binPath / TEXT("FlaxEngine.winmd"));
files.Add(binPath / TEXT("FlaxEngine.xml"));
FileSystem::DirectoryGetFiles(files, binPath, TEXT("*.dll"), DirectorySearchOption::TopDirectoryOnly);
if (data.Configuration != BuildConfiguration::Release)
{
FileSystem::DirectoryGetFiles(files, binPath, TEXT("*.pdb"), DirectorySearchOption::TopDirectoryOnly);
}
for (int32 i = 0; i < files.Count(); i++)
{
if (!FileSystem::FileExists(files[i]))
{
data.Error(TEXT("Missing source file {0}."), files[i]);
return true;
}
if (FileSystem::CopyFile(data.DataOutputPath / StringUtils::GetFileName(files[i]), files[i]))
{
data.Error(TEXT("Failed to setup output directory."));
return true;
}
}
const auto projectName = gameSettings->ProductName;
auto defaultNamespace = projectName;
ScriptsBuilder::FilterNamespaceText(defaultNamespace);
const StringAnsi projectGuid = "{3A9A2246-71DD-4567-9ABF-3E040310E30E}";
const String productId = Guid::New().ToString(Guid::FormatType::D);
const char* mode;
switch (data.Platform)
{
case BuildPlatform::UWPx86:
mode = "x86";
break;
case BuildPlatform::UWPx64:
mode = "x64";
break;
default:
return true;
}
// Prepare certificate
const auto srcCertificatePath = Globals::ProjectFolder / platformSettings->CertificateLocation;
const auto dstCertificatePath = data.DataOutputPath / TEXT("WSACertificate.pfx");
if (platformSettings->CertificateLocation.HasChars() && FileSystem::FileExists(srcCertificatePath))
{
// Use cert from settings
if (FileSystem::CopyFile(dstCertificatePath, srcCertificatePath))
{
data.Error(TEXT("Failed to copy WSACertificate.pfx file."));
return true;
}
}
else
{
// Generate new temp cert if missing
if (!FileSystem::FileExists(dstCertificatePath))
{
if (EditorUtilities::GenerateCertificate(gameSettings->CompanyName, dstCertificatePath))
{
LOG(Warning, "Failed to create certificate.");
}
}
}
// Copy assets
const auto dstAssetsPath = data.DataOutputPath / TEXT("Assets");
const auto srcAssetsPath = uwpDataPath / TEXT("Assets");
if (!FileSystem::DirectoryExists(dstAssetsPath))
{
if (FileSystem::CopyDirectory(dstAssetsPath, srcAssetsPath, true))
{
data.Error(TEXT("Failed to copy Assets directory."));
return true;
}
}
const auto dstPropertiesPath = data.DataOutputPath / TEXT("Properties");
if (!FileSystem::DirectoryExists(dstPropertiesPath))
{
if (FileSystem::CreateDirectory(dstPropertiesPath))
{
data.Error(TEXT("Failed to create Properties directory."));
return true;
}
}
const auto dstDefaultRdXmlPath = dstPropertiesPath / TEXT("Default.rd.xml");
const auto srcDefaultRdXmlPath = uwpDataPath / TEXT("Default.rd.xml");
if (!FileSystem::FileExists(dstDefaultRdXmlPath))
{
if (FileSystem::CopyFile(dstDefaultRdXmlPath, srcDefaultRdXmlPath))
{
data.Error(TEXT("Failed to copy Default.rd.xml file."));
return true;
}
}
const auto dstAssemblyInfoPath = dstPropertiesPath / TEXT("AssemblyInfo.cs");
const auto srcAssemblyInfoPath = uwpDataPath / TEXT("AssemblyInfo.cs");
if (!FileSystem::FileExists(dstAssemblyInfoPath))
{
// Get template
if (File::ReadAllText(srcAssemblyInfoPath, fileTemplate))
{
data.Error(TEXT("Failed to load AssemblyInfo.cs template."));
return true;
}
// Write data to file
auto file = FileWriteStream::Open(dstAssemblyInfoPath);
bool hasError = true;
if (file)
{
auto now = DateTime::Now();
file->WriteText(StringAnsi::Format(
fileTemplate.Get()
, gameSettings->ProductName.ToStringAnsi()
, gameSettings->CompanyName.ToStringAnsi()
, now.GetYear()
));
hasError = file->HasError();
Delete(file);
}
if (hasError)
{
data.Error(TEXT("Failed to create AssemblyInfo.cs."));
return true;
}
}
const auto dstAppPath = data.DataOutputPath / TEXT("App.cs");
const auto srcAppPath = uwpDataPath / TEXT("App.cs");
if (!FileSystem::FileExists(dstAppPath))
{
// Get template
if (File::ReadAllText(srcAppPath, fileTemplate))
{
data.Error(TEXT("Failed to load App.cs template."));
return true;
}
// Write data to file
auto file = FileWriteStream::Open(dstAppPath);
bool hasError = true;
if (file)
{
file->WriteText(StringAnsi::Format(
fileTemplate.Get()
, defaultNamespace.ToStringAnsi() // {0} Default Namespace
));
hasError = file->HasError();
Delete(file);
}
if (hasError)
{
data.Error(TEXT("Failed to create App.cs."));
return true;
}
}
const auto dstFlaxGeneratedPath = data.DataOutputPath / TEXT("FlaxGenerated.cs");
const auto srcFlaxGeneratedPath = uwpDataPath / TEXT("FlaxGenerated.cs");
{
// Get template
if (File::ReadAllText(srcFlaxGeneratedPath, fileTemplate))
{
data.Error(TEXT("Failed to load FlaxGenerated.cs template."));
return true;
}
// Prepare
StringAnsi autoRotationPreferences;
if ((int)platformSettings->AutoRotationPreferences & (int)UWPPlatformSettings::DisplayOrientations::Landscape)
{
autoRotationPreferences += "DisplayOrientations.Landscape";
}
if ((int)platformSettings->AutoRotationPreferences & (int)UWPPlatformSettings::DisplayOrientations::LandscapeFlipped)
{
if (autoRotationPreferences.HasChars())
autoRotationPreferences += " | ";
autoRotationPreferences += "DisplayOrientations.LandscapeFlipped";
}
if ((int)platformSettings->AutoRotationPreferences & (int)UWPPlatformSettings::DisplayOrientations::Portrait)
{
if (autoRotationPreferences.HasChars())
autoRotationPreferences += " | ";
autoRotationPreferences += "DisplayOrientations.Portrait";
}
if ((int)platformSettings->AutoRotationPreferences & (int)UWPPlatformSettings::DisplayOrientations::PortraitFlipped)
{
if (autoRotationPreferences.HasChars())
autoRotationPreferences += " | ";
autoRotationPreferences += "DisplayOrientations.PortraitFlipped";
}
StringAnsi preferredLaunchWindowingMode = platformSettings->PreferredLaunchWindowingMode == UWPPlatformSettings::WindowMode::FullScreen ? "FullScreen" : "PreferredLaunchViewSize";
// Write data to file
auto file = FileWriteStream::Open(dstFlaxGeneratedPath);
bool hasError = true;
if (file)
{
file->WriteText(StringAnsi::Format(
fileTemplate.Get()
, autoRotationPreferences.Get()
, preferredLaunchWindowingMode.Get()
));
hasError = file->HasError();
Delete(file);
}
if (hasError)
{
data.Error(TEXT("Failed to create FlaxGenerated.cs."));
return true;
}
}
// Create solution
const auto dstSolutionPath = data.DataOutputPath / projectName + TEXT(".sln");
const auto srcSolutionPath = uwpDataPath / TEXT("Solution.sln");
if (!FileSystem::FileExists(dstSolutionPath))
{
// Get template
if (File::ReadAllText(srcSolutionPath, fileTemplate))
{
data.Error(TEXT("Failed to load Solution.sln template."));
return true;
}
// Write data to file
auto file = FileWriteStream::Open(dstSolutionPath);
bool hasError = true;
if (file)
{
file->WriteText(StringAnsi::Format(
fileTemplate.Get()
, projectName.ToStringAnsi() // {0} Project Name
, mode // {1} Platform Mode
, projectGuid.ToStringAnsi() // {2} Project ID
));
hasError = file->HasError();
Delete(file);
}
if (hasError)
{
data.Error(TEXT("Failed to create Solution.sln."));
return true;
}
}
// Create project
const auto dstProjectPath = data.DataOutputPath / projectName + TEXT(".csproj");
const auto srcProjectPath = uwpDataPath / TEXT("Project.csproj");
{
// Get template
if (File::ReadAllText(srcProjectPath, fileTemplate))
{
data.Error(TEXT("Failed to load Project.csproj template."));
return true;
}
// Build included files data
StringBuilder filesInclude(2048);
for (int32 i = 0; i < files.Count(); i++)
{
// Link dlls (except FlaxEngine.dll because it's linked as a reference)
if (files[i].EndsWith(TEXT(".dll")) && !files[i].EndsWith(TEXT("FlaxEngine.dll")))
{
String filename = StringUtils::GetFileName(files[i]);
filename.Replace(TEXT('/'), TEXT('\\'), StringSearchCase::CaseSensitive);
filesInclude.Append(TEXT("\n <Content Include=\""));
filesInclude.Append(filename);
filesInclude.Append(TEXT("\" />"));
}
}
// Write data to file
auto file = FileWriteStream::Open(dstProjectPath);
bool hasError = true;
if (file)
{
file->WriteText(StringAnsi::Format(
fileTemplate.Get()
, projectName.ToStringAnsi() // {0} Project Name
, mode // {1} Platform Mode
, projectGuid.Get() // {2} Project ID
, filesInclude.ToString().ToStringAnsi() // {3} Files to include
, defaultNamespace.ToStringAnsi() // {4} Default Namespace
));
hasError = file->HasError();
Delete(file);
}
if (hasError)
{
data.Error(TEXT("Failed to create Project.csproj."));
return true;
}
}
// Create manifest
const auto dstManifestPath = data.DataOutputPath / TEXT("Package.appxmanifest");
const auto srcManifestPath = uwpDataPath / TEXT("Package.appxmanifest");
if (!FileSystem::FileExists(dstManifestPath))
{
// Get template
if (File::ReadAllText(srcManifestPath, fileTemplate))
{
data.Error(TEXT("Failed to load Package.appxmanifest template."));
return true;
}
// Build included files data
StringBuilder filesInclude(2048);
for (int32 i = 0; i < files.Count(); i++)
{
if (files[i].EndsWith(TEXT(".dll")))
{
String filename = StringUtils::GetFileName(files[i]);
filename.Replace(TEXT('/'), TEXT('\\'), StringSearchCase::CaseSensitive);
filesInclude.Append(TEXT("\n <Content Include=\""));
filesInclude.Append(filename);
filesInclude.Append(TEXT("\" />"));
}
}
// Write data to file
auto file = FileWriteStream::Open(dstManifestPath);
bool hasError = true;
if (file)
{
file->WriteText(StringAnsi::Format(
fileTemplate.Get()
, projectName.ToStringAnsi() // {0} Display Name
, gameSettings->CompanyName.ToStringAnsi() // {1} Company Name
, productId.ToStringAnsi() // {2} Product ID
, defaultNamespace.ToStringAnsi() // {3} Default Namespace
));
hasError = file->HasError();
Delete(file);
}
if (hasError)
{
data.Error(TEXT("Failed to create Package.appxmanifest."));
return true;
}
}
return false;
}
bool UWPPlatformTools::OnPostProcess(CookingData& data)
{
LOG(Error, "UWP (Windows Store) platform has been deprecated and soon will be removed!");
// Special case for UWP
// FlaxEngine.dll cannot be added to the solution as `Content` item (due to conflicts with C++ /CX FlaxEngine.dll)
// Use special directory for it (generated UWP project handles this case and copies lib to the output)
const String assembliesPath = data.DataOutputPath;
const auto dstPath1 = data.DataOutputPath / TEXT("DataSecondary");
if (!FileSystem::DirectoryExists(dstPath1))
{
if (FileSystem::CreateDirectory(dstPath1))
{
data.Error(TEXT("Failed to create DataSecondary directory."));
return true;
}
}
if (FileSystem::MoveFile(dstPath1 / TEXT("FlaxEngine.dll"), assembliesPath / TEXT("FlaxEngine.dll"), true))
{
data.Error(TEXT("Failed to move FlaxEngine.dll to DataSecondary directory."));
return true;
}
return false;
}
#endif