Implement C# AOT process for .NET 7 for Windows platform
This commit is contained in:
@@ -154,6 +154,32 @@ API_ENUM() enum class BuildConfiguration
|
||||
|
||||
extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuration);
|
||||
|
||||
/// <summary>
|
||||
/// .NET Ahead of Time Compilation (AOT) modes.
|
||||
/// </summary>
|
||||
enum class DotNetAOTModes
|
||||
{
|
||||
/// <summary>
|
||||
/// AOT is not used.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Use .NET Native IL Compiler (shorten as ILC) to convert all C# assemblies in native platform executable binary.
|
||||
/// </summary>
|
||||
ILC,
|
||||
|
||||
/// <summary>
|
||||
/// Use Mono AOT to cross-compile all used C# assemblies into native platform shared libraries.
|
||||
/// </summary>
|
||||
MonoAOTDynamic,
|
||||
|
||||
/// <summary>
|
||||
/// Use Mono AOT to cross-compile all used C# assemblies into native platform static libraries which can be linked into a single shared library.
|
||||
/// </summary>
|
||||
MonoAOTStatic,
|
||||
};
|
||||
|
||||
#define BUILD_STEP_CANCEL_CHECK if (GameCooker::IsCancelRequested()) return true
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -168,6 +168,16 @@ const Char* ToString(const BuildConfiguration configuration)
|
||||
}
|
||||
}
|
||||
|
||||
bool PlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
|
||||
{
|
||||
const String filename = StringUtils::GetFileName(file);
|
||||
if (filename.Contains(TEXT(".CSharp")) ||
|
||||
filename.Contains(TEXT("Newtonsoft.Json")))
|
||||
return false;
|
||||
// TODO: maybe use Mono.Cecil via Flax.Build to read assembly image metadata and check if it contains C#?
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& other) const
|
||||
{
|
||||
if (ContentSize != other.ContentSize)
|
||||
@@ -636,9 +646,9 @@ bool GameCookerImpl::Build()
|
||||
|
||||
// Build Started
|
||||
CallEvent(GameCooker::EventType::BuildStarted);
|
||||
data.Tools->OnBuildStarted(data);
|
||||
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
|
||||
Steps[stepIndex]->OnBuildStarted(data);
|
||||
data.Tools->OnBuildStarted(data);
|
||||
data.InitProgress(Steps.Count());
|
||||
|
||||
// Execute all steps in a sequence
|
||||
@@ -705,9 +715,9 @@ bool GameCookerImpl::Build()
|
||||
}
|
||||
IsRunning = false;
|
||||
CancelFlag = 0;
|
||||
data.Tools->OnBuildEnded(data, failed);
|
||||
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
|
||||
Steps[stepIndex]->OnBuildEnded(data, failed);
|
||||
data.Tools->OnBuildEnded(data, failed);
|
||||
CallEvent(failed ? GameCooker::EventType::BuildFailed : GameCooker::EventType::BuildDone);
|
||||
Delete(Data);
|
||||
Data = nullptr;
|
||||
|
||||
@@ -116,8 +116,6 @@ void AndroidPlatformTools::OnBuildStarted(CookingData& data)
|
||||
data.DataOutputPath /= TEXT("app/assets");
|
||||
data.NativeCodeOutputPath /= TEXT("app/assets");
|
||||
data.ManagedCodeOutputPath /= TEXT("app/assets");
|
||||
|
||||
PlatformTools::OnBuildStarted(data);
|
||||
}
|
||||
|
||||
bool AndroidPlatformTools::OnPostProcess(CookingData& data)
|
||||
|
||||
@@ -37,9 +37,9 @@ GDKPlatformTools::GDKPlatformTools()
|
||||
}
|
||||
}
|
||||
|
||||
bool GDKPlatformTools::UseAOT() const
|
||||
DotNetAOTModes GDKPlatformTools::UseAOT() const
|
||||
{
|
||||
return true;
|
||||
return DotNetAOTModes::MonoAOTDynamic;
|
||||
}
|
||||
|
||||
bool GDKPlatformTools::OnScriptsStepDone(CookingData& data)
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
public:
|
||||
|
||||
// [PlatformTools]
|
||||
bool UseAOT() const override;
|
||||
DotNetAOTModes UseAOT() const override;
|
||||
bool OnScriptsStepDone(CookingData& data) override;
|
||||
bool OnDeployBinaries(CookingData& data) override;
|
||||
void OnConfigureAOT(CookingData& data, AotConfig& config) override;
|
||||
|
||||
@@ -37,9 +37,9 @@ ArchitectureType UWPPlatformTools::GetArchitecture() const
|
||||
return _arch;
|
||||
}
|
||||
|
||||
bool UWPPlatformTools::UseAOT() const
|
||||
DotNetAOTModes UWPPlatformTools::UseAOT() const
|
||||
{
|
||||
return true;
|
||||
return DotNetAOTModes::MonoAOTDynamic;
|
||||
}
|
||||
|
||||
bool UWPPlatformTools::OnScriptsStepDone(CookingData& data)
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
const Char* GetName() const override;
|
||||
PlatformType GetPlatform() const override;
|
||||
ArchitectureType GetArchitecture() const override;
|
||||
bool UseAOT() const override;
|
||||
DotNetAOTModes UseAOT() const override;
|
||||
bool OnScriptsStepDone(CookingData& data) override;
|
||||
bool OnDeployBinaries(CookingData& data) override;
|
||||
void OnConfigureAOT(CookingData& data, AotConfig& config) override;
|
||||
|
||||
@@ -15,7 +15,6 @@ class TextureBase;
|
||||
class FLAXENGINE_API PlatformTools
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="PlatformTools"/> class.
|
||||
/// </summary>
|
||||
@@ -44,9 +43,9 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the value indicating whenever platform requires AOT (needs C# assemblies to be precompiled).
|
||||
/// </summary>
|
||||
virtual bool UseAOT() const
|
||||
virtual DotNetAOTModes UseAOT() const
|
||||
{
|
||||
return false;
|
||||
return DotNetAOTModes::None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,13 +74,9 @@ public:
|
||||
/// <param name="data">The cooking data.</param>
|
||||
/// <param name="file">The file path.</param>
|
||||
/// <returns>True if it's a native file, otherwise false.<returns>
|
||||
virtual bool IsNativeCodeFile(CookingData& data, const String& file)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool IsNativeCodeFile(CookingData& data, const String& file);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Called when game building starts.
|
||||
/// </summary>
|
||||
|
||||
@@ -204,7 +204,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
|
||||
// Assume FlaxGame was prebuilt for target platform
|
||||
args += TEXT(" -SkipTargets=FlaxGame");
|
||||
}
|
||||
for (auto& define : data.CustomDefines)
|
||||
for (const String& define : data.CustomDefines)
|
||||
{
|
||||
args += TEXT(" -D");
|
||||
args += define;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "Engine/Renderer/AntiAliasing/SMAA.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Editor/Cooker/PlatformTools.h"
|
||||
#include "Editor/Utilities/EditorUtilities.h"
|
||||
|
||||
bool DeployDataStep::Perform(CookingData& data)
|
||||
{
|
||||
@@ -31,30 +32,20 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
FileSystem::CreateDirectory(contentDir);
|
||||
const String dstMono = data.DataOutputPath / TEXT("Mono");
|
||||
#if USE_NETCORE
|
||||
// TODO: Optionally copy all files needed for self-contained deployment
|
||||
{
|
||||
// Remove old Mono files
|
||||
FileSystem::DeleteDirectory(dstMono);
|
||||
FileSystem::DeleteFile(data.DataOutputPath / TEXT("MonoPosixHelper.dll"));
|
||||
}
|
||||
#else
|
||||
if (!FileSystem::DirectoryExists(dstMono))
|
||||
String dstDotnet = data.DataOutputPath / TEXT("Dotnet");
|
||||
const DotNetAOTModes aotMode = data.Tools->UseAOT();
|
||||
const bool usAOT = aotMode != DotNetAOTModes::None;
|
||||
if (usAOT)
|
||||
{
|
||||
// Deploy Mono files (from platform data folder)
|
||||
const String srcMono = depsRoot / TEXT("Mono");
|
||||
if (!FileSystem::DirectoryExists(srcMono))
|
||||
{
|
||||
data.Error(TEXT("Missing Mono runtime data files."));
|
||||
return true;
|
||||
}
|
||||
if (FileSystem::CopyDirectory(dstMono, srcMono, true))
|
||||
{
|
||||
data.Error(TEXT("Failed to copy Mono runtime data files."));
|
||||
return true;
|
||||
}
|
||||
// Deploy Dotnet files into intermediate cooking directory for AOT
|
||||
FileSystem::DeleteDirectory(dstDotnet);
|
||||
dstDotnet = data.ManagedCodeOutputPath;
|
||||
}
|
||||
#endif
|
||||
const String dstDotnet = data.DataOutputPath / TEXT("Dotnet");
|
||||
if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet())
|
||||
{
|
||||
// Use system-installed .Net Runtime
|
||||
@@ -69,7 +60,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
{
|
||||
// Use prebuilt .Net installation for that platform
|
||||
LOG(Info, "Using .Net Runtime {} at {}", data.Tools->GetName(), srcDotnet);
|
||||
if (FileSystem::CopyDirectory(dstDotnet, srcDotnet, true))
|
||||
if (EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet, true))
|
||||
{
|
||||
data.Error(TEXT("Failed to copy .Net runtime data files."));
|
||||
return true;
|
||||
@@ -92,7 +83,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Mac;
|
||||
break;
|
||||
}
|
||||
if (canUseSystemDotnet)
|
||||
if (canUseSystemDotnet && (aotMode == DotNetAOTModes::None || aotMode == DotNetAOTModes::ILC))
|
||||
{
|
||||
// Ask Flax.Build to provide .Net SDK location for the current platform
|
||||
String sdks;
|
||||
@@ -130,6 +121,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
}
|
||||
Sorting::QuickSort(versions.Get(), versions.Count());
|
||||
const String version = versions.Last();
|
||||
FileSystem::NormalizePath(srcDotnet);
|
||||
LOG(Info, "Using .Net Runtime {} at {}", version, srcDotnet);
|
||||
|
||||
// Deploy runtime files
|
||||
@@ -137,8 +129,15 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), srcDotnet / TEXT("LICENSE.TXT"));
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), srcDotnet / TEXT("ThirdPartyNotices.txt"));
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), srcDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"));
|
||||
failed |= FileSystem::CopyDirectory(dstDotnet / TEXT("host/fxr") / version, srcDotnet / TEXT("host/fxr") / version, true);
|
||||
failed |= FileSystem::CopyDirectory(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / version, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
|
||||
if (usAOT)
|
||||
{
|
||||
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("host/fxr") / version, srcDotnet / TEXT("host/fxr") / version, true);
|
||||
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / version, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
|
||||
}
|
||||
if (failed)
|
||||
{
|
||||
data.Error(TEXT("Failed to copy .Net runtime data files."));
|
||||
@@ -166,16 +165,41 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
|
||||
return true;
|
||||
}
|
||||
FileSystem::NormalizePath(srcDotnet);
|
||||
LOG(Info, "Using .Net Runtime {} at {}", TEXT("Host"), srcDotnet);
|
||||
|
||||
// Deploy runtime files
|
||||
const String packFolder = srcDotnet / TEXT("../../../");
|
||||
const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll");
|
||||
const bool srcDotnetFromEngine = srcDotnet.Contains(TEXT("Source/Platforms"));
|
||||
String packFolder = srcDotnet / TEXT("../../../");
|
||||
String dstDotnetLibs = dstDotnet, srcDotnetLibs = srcDotnet;
|
||||
StringUtils::PathRemoveRelativeParts(packFolder);
|
||||
if (usAOT)
|
||||
{
|
||||
// AOT runtime files inside Engine Platform folder
|
||||
packFolder /= TEXT("Dotnet");
|
||||
dstDotnetLibs /= TEXT("lib/net7.0");
|
||||
srcDotnetLibs = packFolder / TEXT("lib/net7.0");
|
||||
}
|
||||
else if (srcDotnetFromEngine)
|
||||
{
|
||||
// Runtime files inside Engine Platform folder
|
||||
dstDotnetLibs /= TEXT("lib/net7.0");
|
||||
srcDotnetLibs /= TEXT("lib/net7.0");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Runtime files inside Dotnet SDK folder
|
||||
dstDotnetLibs /= TEXT("shared/Microsoft.NETCore.App");
|
||||
srcDotnetLibs /= TEXT("../lib/net7.0");
|
||||
}
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.txt"));
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.TXT"));
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("ThirdPartyNotices.txt"));
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("THIRD-PARTY-NOTICES.TXT"));
|
||||
failed |= FileSystem::CopyDirectory(dstDotnet / TEXT("shared/Microsoft.NETCore.App"), srcDotnet / TEXT("../lib/net7.0"), true);
|
||||
failed |= FileSystem::CopyFile(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / TEXT("System.Private.CoreLib.dll"), srcDotnet / TEXT("System.Private.CoreLib.dll"));
|
||||
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnetLibs, srcDotnetLibs, true);
|
||||
if (FileSystem::FileExists(srcDotnet / corlibPrivateName))
|
||||
failed |= EditorUtilities::CopyFileIfNewer(dstDotnetLibs / corlibPrivateName, srcDotnet / corlibPrivateName);
|
||||
switch (data.Platform)
|
||||
{
|
||||
case BuildPlatform::AndroidARM64:
|
||||
@@ -202,6 +226,23 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!FileSystem::DirectoryExists(dstMono))
|
||||
{
|
||||
// Deploy Mono files (from platform data folder)
|
||||
const String srcMono = depsRoot / TEXT("Mono");
|
||||
if (!FileSystem::DirectoryExists(srcMono))
|
||||
{
|
||||
data.Error(TEXT("Missing Mono runtime data files."));
|
||||
return true;
|
||||
}
|
||||
if (FileSystem::CopyDirectory(dstMono, srcMono, true))
|
||||
{
|
||||
data.Error(TEXT("Failed to copy Mono runtime data files."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Deploy engine data for the target platform
|
||||
if (data.Tools->OnDeployBinaries(data))
|
||||
|
||||
@@ -1,25 +1,82 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "PrecompileAssembliesStep.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Config/BuildSettings.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
#include "Editor/Cooker/PlatformTools.h"
|
||||
#include "Editor/Utilities/EditorUtilities.h"
|
||||
|
||||
void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
|
||||
{
|
||||
const DotNetAOTModes aotMode = data.Tools->UseAOT();
|
||||
if (aotMode == DotNetAOTModes::None)
|
||||
return;
|
||||
|
||||
// Redirect C# assemblies to intermediate cooking directory (processed by ILC)
|
||||
data.ManagedCodeOutputPath = data.CacheDirectory / TEXT("AOTAssemblies");
|
||||
}
|
||||
|
||||
bool PrecompileAssembliesStep::Perform(CookingData& data)
|
||||
{
|
||||
// Skip for some platforms
|
||||
if (!data.Tools->UseAOT())
|
||||
const DotNetAOTModes aotMode = data.Tools->UseAOT();
|
||||
if (aotMode == DotNetAOTModes::None)
|
||||
return false;
|
||||
const auto& buildSettings = *BuildSettings::Get();
|
||||
if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet())
|
||||
return false;
|
||||
LOG(Info, "Using AOT...");
|
||||
|
||||
// Useful references about AOT:
|
||||
// http://www.mono-project.com/docs/advanced/runtime/docs/aot/
|
||||
// http://www.mono-project.com/docs/advanced/aot/
|
||||
|
||||
const String infoMsg = TEXT("Running AOT");
|
||||
data.StepProgress(infoMsg, 0);
|
||||
|
||||
// Override Newtonsoft.Json with AOT-version (one that doesn't use System.Reflection.Emit)
|
||||
EditorUtilities::CopyFileIfNewer(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.dll"), Globals::StartupFolder / TEXT("Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll"));
|
||||
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.xml"));
|
||||
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.pdb"));
|
||||
|
||||
// Run AOT by Flax.Build
|
||||
const Char *platform, *architecture, *configuration = ::ToString(data.Configuration);
|
||||
data.GetBuildPlatformName(platform, architecture);
|
||||
const String logFile = data.CacheDirectory / TEXT("AotLog.txt");
|
||||
const Char* aotModeName = TEXT("");
|
||||
switch (aotMode)
|
||||
{
|
||||
case DotNetAOTModes::ILC:
|
||||
aotModeName = TEXT("ILC");
|
||||
break;
|
||||
case DotNetAOTModes::MonoAOTDynamic:
|
||||
aotModeName = TEXT("MonoAOTDynamic");
|
||||
break;
|
||||
case DotNetAOTModes::MonoAOTStatic:
|
||||
aotModeName = TEXT("MonoAOTStatic");
|
||||
break;
|
||||
}
|
||||
auto args = String::Format(
|
||||
TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\""),
|
||||
logFile, platform, architecture, configuration, aotModeName, data.DataOutputPath, data.ManagedCodeOutputPath);
|
||||
for (const String& define : data.CustomDefines)
|
||||
{
|
||||
args += TEXT(" -D");
|
||||
args += define;
|
||||
}
|
||||
if (ScriptsBuilder::RunBuildTool(args))
|
||||
{
|
||||
data.Error(TEXT("Failed to precompile game scripts."));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
// Useful references about AOT:
|
||||
// https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/README.md
|
||||
// https://github.com/dotnet/runtime/blob/main/docs/workflow/building/coreclr/nativeaot.md
|
||||
// https://github.com/dotnet/samples/tree/main/core/nativeaot/NativeLibrary
|
||||
// http://www.mono-project.com/docs/advanced/runtime/docs/aot/
|
||||
// http://www.mono-project.com/docs/advanced/aot/
|
||||
|
||||
// Setup
|
||||
// TODO: remove old AotConfig, OnConfigureAOT, OnPerformAOT and OnPostProcessAOT
|
||||
PlatformTools::AotConfig config(data);
|
||||
data.Tools->OnConfigureAOT(data, config);
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
#include "Editor/Cooker/GameCooker.h"
|
||||
|
||||
/// <summary>
|
||||
/// Optional step used only on selected platform that precompiles C# script assemblies.
|
||||
/// Uses Mono Ahead of Time Compilation (AOT) feature.
|
||||
/// Optional step used only on selected platform that precompiles C# script assemblies. Uses Ahead of Time Compilation (AOT) feature.
|
||||
/// </summary>
|
||||
/// <seealso cref="GameCooker::BuildStep" />
|
||||
class PrecompileAssembliesStep : public GameCooker::BuildStep
|
||||
@@ -14,5 +13,6 @@ class PrecompileAssembliesStep : public GameCooker::BuildStep
|
||||
public:
|
||||
|
||||
// [BuildStep]
|
||||
void OnBuildStarted(CookingData& data) override;
|
||||
bool Perform(CookingData& data) override;
|
||||
};
|
||||
|
||||
@@ -820,3 +820,49 @@ bool EditorUtilities::ReplaceInFile(const StringView& file, const StringView& fi
|
||||
text.Replace(findWhat.Get(), findWhat.Length(), replaceWith.Get(), replaceWith.Length());
|
||||
return File::WriteAllText(file, text, Encoding::ANSI);
|
||||
}
|
||||
|
||||
bool EditorUtilities::CopyFileIfNewer(const StringView& dst, const StringView& src)
|
||||
{
|
||||
if (FileSystem::FileExists(dst) &&
|
||||
FileSystem::GetFileLastEditTime(src) <= FileSystem::GetFileLastEditTime(dst) &&
|
||||
FileSystem::GetFileSize(dst) == FileSystem::GetFileSize(src))
|
||||
return false;
|
||||
return FileSystem::CopyFile(dst, src);
|
||||
}
|
||||
|
||||
bool EditorUtilities::CopyDirectoryIfNewer(const StringView& dst, const StringView& src, bool withSubDirectories)
|
||||
{
|
||||
if (FileSystem::DirectoryExists(dst))
|
||||
{
|
||||
// Copy all files
|
||||
Array<String> cache(32);
|
||||
if (FileSystem::DirectoryGetFiles(cache, *src, TEXT("*"), DirectorySearchOption::TopDirectoryOnly))
|
||||
return true;
|
||||
for (int32 i = 0; i < cache.Count(); i++)
|
||||
{
|
||||
String dstFile = String(dst) / StringUtils::GetFileName(cache[i]);
|
||||
if (CopyFileIfNewer(*dstFile, *cache[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copy all subdirectories (if need to)
|
||||
if (withSubDirectories)
|
||||
{
|
||||
cache.Clear();
|
||||
if (FileSystem::GetChildDirectories(cache, src))
|
||||
return true;
|
||||
for (int32 i = 0; i < cache.Count(); i++)
|
||||
{
|
||||
String dstDir = String(dst) / StringUtils::GetFileName(cache[i]);
|
||||
if (CopyDirectoryIfNewer(dstDir, cache[i], true))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FileSystem::CopyDirectory(dst, src, withSubDirectories);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,7 @@ public:
|
||||
/// <param name="replaceWith">The value to replace to.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
static bool ReplaceInFile(const StringView& file, const StringView& findWhat, const StringView& replaceWith);
|
||||
|
||||
static bool CopyFileIfNewer(const StringView& dst, const StringView& src);
|
||||
static bool CopyDirectoryIfNewer(const StringView& dst, const StringView& src, bool withSubDirectories);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user