Files
FlaxEngine/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
2021-07-21 15:10:39 +02:00

527 lines
19 KiB
C++

// Copyright (c) 2012-2019 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/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_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform);
bool UWPPlatformTools::UseAOT() const
{
return true;
}
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)
{
bool isXboxOne = data.Platform == BuildPlatform::XboxOne;
const auto platformDataPath = Globals::StartupFolder / TEXT("Source/Platforms");
const auto uwpDataPath = platformDataPath / (isXboxOne ? TEXT("XboxOne") : TEXT("UWP")) / TEXT("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:
case BuildPlatform::XboxOne:
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->WriteTextFormatted(
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->WriteTextFormatted(
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";
if (isXboxOne)
preferredLaunchWindowingMode = "FullScreen";
// Write data to file
auto file = FileWriteStream::Open(dstFlaxGeneratedPath);
bool hasError = true;
if (file)
{
file->WriteTextFormatted(
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->WriteTextFormatted(
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->WriteTextFormatted(
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->WriteTextFormatted(
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;
}
void UWPPlatformTools::OnConfigureAOT(CookingData& data, AotConfig& config)
{
const auto platformDataPath = data.GetPlatformBinariesRoot();
const bool useInterpreter = true; // TODO: support using Full AOT instead of interpreter
const bool enableDebug = data.Configuration != BuildConfiguration::Release;
const Char* aotMode = useInterpreter ? TEXT("full,interp") : TEXT("full");
const Char* debugMode = enableDebug ? TEXT("soft-debug") : TEXT("nodebug");
config.AotCompilerArgs = String::Format(TEXT("--aot={0},verbose,stats,print-skipped,{1} -O=all"),
aotMode,
debugMode);
if (enableDebug)
config.AotCompilerArgs = TEXT("--debug ") + config.AotCompilerArgs;
config.AotCompilerPath = platformDataPath / TEXT("Tools/mono.exe");
}
bool UWPPlatformTools::OnPerformAOT(CookingData& data, AotConfig& config, const String& assemblyPath)
{
// Skip .dll.dll which could be a false result from the previous AOT which could fail
if (assemblyPath.EndsWith(TEXT(".dll.dll")))
{
LOG(Warning, "Skip AOT for file '{0}' as it can be a result from the previous task", assemblyPath);
return false;
}
// Check if skip this assembly (could be already processed)
const String filename = StringUtils::GetFileName(assemblyPath);
const String outputPath = config.AotCachePath / filename + TEXT(".dll");
if (FileSystem::FileExists(outputPath) && FileSystem::GetFileLastEditTime(assemblyPath) < FileSystem::GetFileLastEditTime(outputPath))
return false;
LOG(Info, "Calling AOT tool for \"{0}\"", assemblyPath);
// Cleanup temporary results (fromm the previous AT that fail or sth)
const String resultPath = assemblyPath + TEXT(".dll");
const String resultPathExp = resultPath + TEXT(".exp");
const String resultPathLib = resultPath + TEXT(".lib");
const String resultPathPdb = resultPath + TEXT(".pdb");
if (FileSystem::FileExists(resultPath))
FileSystem::DeleteFile(resultPath);
if (FileSystem::FileExists(resultPathExp))
FileSystem::DeleteFile(resultPathExp);
if (FileSystem::FileExists(resultPathLib))
FileSystem::DeleteFile(resultPathLib);
if (FileSystem::FileExists(resultPathPdb))
FileSystem::DeleteFile(resultPathPdb);
// Call tool
String workingDir = StringUtils::GetDirectoryName(config.AotCompilerPath);
String command = String::Format(TEXT("\"{0}\" {1} \"{2}\""), config.AotCompilerPath, config.AotCompilerArgs, assemblyPath);
const int32 result = Platform::RunProcess(command, workingDir, config.EnvVars);
if (result != 0)
{
data.Error(TEXT("AOT tool execution failed with result code {1} for assembly \"{0}\". See log for more info."), assemblyPath, result);
return true;
}
// Copy result
if (FileSystem::CopyFile(outputPath, resultPath))
{
data.Error(TEXT("Failed to copy the AOT tool result file. It can be missing."));
return true;
}
// Copy pdb file if exists
if (data.Configuration != BuildConfiguration::Release && FileSystem::FileExists(resultPathPdb))
{
FileSystem::CopyFile(config.AotCachePath / StringUtils::GetFileName(resultPathPdb), resultPathPdb);
}
// Clean intermediate results
if (FileSystem::DeleteFile(resultPath)
|| (FileSystem::FileExists(resultPathExp) && FileSystem::DeleteFile(resultPathExp))
|| (FileSystem::FileExists(resultPathLib) && FileSystem::DeleteFile(resultPathLib))
|| (FileSystem::FileExists(resultPathPdb) && FileSystem::DeleteFile(resultPathPdb))
)
{
LOG(Warning, "Failed to remove the AOT tool result file(s).");
}
return false;
}
bool UWPPlatformTools::OnPostProcess(CookingData& data)
{
// 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;
}
const Char* WSAPlatformTools::GetDisplayName() const
{
return TEXT("Windows Store");
}
const Char* WSAPlatformTools::GetName() const
{
return TEXT("UWP");
}
PlatformType WSAPlatformTools::GetPlatform() const
{
return PlatformType::UWP;
}
ArchitectureType WSAPlatformTools::GetArchitecture() const
{
return _arch;
}
#endif