diff --git a/Private/SourceCodeGenerator.cpp b/Private/SourceCodeGenerator.cpp new file mode 100644 index 0000000..4051572 --- /dev/null +++ b/Private/SourceCodeGenerator.cpp @@ -0,0 +1,964 @@ +#include "SourceCodeGenerator.h" +#include "Features/IModularFeatures.h" +#include "Misc/FileHelper.h" +#include "UObject/CoreNet.h" +#include "UObject/Interface.h" + +#define LOCTEXT_NAMESPACE "FSourceCodeGeneratorModule" + +void FSourceCodeGeneratorModule::StartupModule() +{ + IModularFeatures::Get().RegisterModularFeature(TEXT("ScriptGenerator"), this); +} + +void FSourceCodeGeneratorModule::ShutdownModule() +{ + IModularFeatures::Get().UnregisterModularFeature(TEXT("ScriptGenerator"), this); +} + +FString FSourceCodeGeneratorModule::GetGeneratedCodeModuleName() const +{ + return TEXT("UnrealCSharp"); +} + +bool FSourceCodeGeneratorModule::SupportsTarget(const FString& TargetName) const +{ + const auto SettingFile = FPaths::Combine( + FPaths::ConvertRelativePathToFull(FPaths::GetPath(FPaths::GetProjectFilePath())), + TEXT("Config/DefaultUnrealCSharpEditorSetting.ini")); + + auto bEnableExport = false; + + if (FPaths::FileExists(SettingFile)) + { + GConfig->GetBool( + TEXT("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting"), + TEXT("bEnableExport"), + bEnableExport, + SettingFile); + } + + return bEnableExport; +} + +bool FSourceCodeGeneratorModule::ShouldExportClassesForModule(const FString& ModuleName, + EBuildModuleType::Type ModuleType, + const FString& ModuleGeneratedIncludeDirectory) const +{ + return ExportModules.Contains(ModuleName) && + (ModuleType == EBuildModuleType::EngineRuntime || ModuleType == EBuildModuleType::GameRuntime); +} + +void FSourceCodeGeneratorModule::Initialize(const FString& RootLocalPath, const FString& RootBuildPath, + const FString& OutputDirectory, const FString& IncludeBase) +{ + OutputPath = OutputDirectory; + + const auto ProjectPath = FPaths::ConvertRelativePathToFull(FPaths::GetPath(FPaths::GetProjectFilePath())); + + const auto ProjectSourcePath = FPaths::Combine(ProjectPath, TEXT("Source/")); + + const auto ProjectPluginsPath = FPaths::Combine(ProjectPath, TEXT("Plugins/")); + + const auto EngineSourcePath = FPaths::EngineSourceDir(); + + const auto EnginePluginsPath = FPaths::EnginePluginsDir(); + + const auto ConfigFilePath = FPaths::Combine(ProjectPath, TEXT("Config/DefaultUnrealCSharpEditorSetting.ini")); + + const auto ConfigSection = FString(TEXT("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting")); + + GetModules(ProjectSourcePath, Project); + + GetModules(ProjectSourcePath, HeaderPath); + + GetModules(ProjectPluginsPath, HeaderPath); + + GetPlugins(ProjectPluginsPath, HeaderPath); + + GetModules(FPaths::Combine(EngineSourcePath, TEXT("Developer/")), HeaderPath); + + GetModules(FPaths::Combine(EngineSourcePath, TEXT("Editor/")), HeaderPath); + + GetModules(FPaths::Combine(EngineSourcePath, TEXT("Programs/")), HeaderPath); + + GetModules(FPaths::Combine(EngineSourcePath, TEXT("Runtime/")), HeaderPath); + + GetModules(EnginePluginsPath, HeaderPath); + + GetPlugins(EnginePluginsPath, HeaderPath); + + if (FPaths::FileExists(ConfigFilePath)) + { + GConfig->GetArray(*ConfigSection, TEXT("+ExportModule"), ExportModules, ConfigFilePath); + } +} + +void FSourceCodeGeneratorModule::ExportClass(UClass* Class, const FString& SourceHeaderFilename, + const FString& GeneratedHeaderFilename, bool bHasChanged) +{ + if (CanExportClass(Class)) + { + TSet DependencyClasses = {Class}; + + FBigStringBuilder StringBuilder; + + FBigStringBuilder BodyBuilder; + + StringBuilder.Append(TEXT( + "#pragma once\r\n\r\n" + "PRAGMA_DISABLE_DEPRECATION_WARNINGS\r\n\r\n")); + + BodyBuilder.Appendf(TEXT( + "namespace\r\n" + "{\r\n" + "\tstruct FRegister%s\r\n" + "\t{\r\n" + "\t\tFRegister%s()\r\n" + "\t\t{\r\n" + "\t\t\tTBindingClassBuilder<%s>(NAMESPACE_BINDING)\r\n" + ), + *Class->GetName(), + *Class->GetName(), + *(Class->GetPrefixCPP() + Class->GetName()) + ); + + for (TFieldIterator Property(Class, EFieldIterationFlags::None); Property; ++Property) + { + if (CanExportProperty(*Property)) + { + GetDependencyClasses(*Property, DependencyClasses); + + ExportProperty(BodyBuilder, Class, *Property); + } + } + + for (TFieldIterator Function(Class, EFieldIterationFlags::None); Function; ++Function) + { + if (CanExportFunction(*Function)) + { + for (TFieldIterator Property(*Function); Property; ++Property) + { + GetDependencyClasses(*Property, DependencyClasses); + } + + ExportFunction(BodyBuilder, Class, *Function); + } + } + + for (const auto DependencyClass : DependencyClasses) + { + StringBuilder.Append(GetInclude(DependencyClass)); + } + + StringBuilder.Append(TEXT("\r\n")); + + BodyBuilder.RemoveSuffix(2); + + BodyBuilder.Appendf(TEXT( + ";\r\n" + "\t\t}\r\n" + "\t};\r\n" + "\r\n" + "\t[[maybe_unused]] FRegister%s Register%s;\r\n" + "}\r\n\r\n" + ), + *Class->GetName(), + *Class->GetName() + ); + + StringBuilder.Append(BodyBuilder); + + StringBuilder.Append(TEXT("PRAGMA_ENABLE_DEPRECATION_WARNINGS\r\n")); + + const auto FilePath = FPaths::Combine(OutputPath, *Class->GetName() + BindingSuffix); + + SaveIfChanged(FilePath, StringBuilder.ToString()); + + ExportClasses.AddUnique(Class); + } +} + +void FSourceCodeGeneratorModule::FinishExport() +{ + TMap> Packages; + + for (const auto ExportClass : ExportClasses) + { + Packages.FindOrAdd(ExportClass->GetPackage()).Add(*ExportClass->GetName()); + } + + for (auto& [Key, Value] : Packages) + { + Value.Sort(); + + FBigStringBuilder StringBuilder; + + StringBuilder.Append(TEXT("#pragma once\r\n\r\n")); + + for (const auto& Name : Value) + { + StringBuilder.Append(GenerateInclude(*Name + BindingSuffix)); + } + + const auto FilePath = FPaths::Combine( + OutputPath, *FPaths::GetCleanFilename(Key->GetName()) + HeaderSuffix); + + SaveIfChanged(FilePath, StringBuilder.ToString()); + } +} + +FString FSourceCodeGeneratorModule::GetGeneratorName() const +{ + return TEXT("UnrealCSharp Source Code Generator"); +} + +bool FSourceCodeGeneratorModule::CanExportClass(const UClass* Class) +{ + return !Class->HasAnyClassFlags(CLASS_Interface) && + IsClassTypeSupported(Class); +} + +bool FSourceCodeGeneratorModule::CanExportFunction(const UFunction* Function) +{ + if (const auto OwnerClass = Function->GetOwnerClass()) + { + if (!OwnerClass->HasAnyClassFlags(CLASS_RequiredAPI) && + !Function->HasAnyFunctionFlags(FUNC_RequiredAPI)) + { + return false; + } + } + + if (Function->HasAnyFunctionFlags(FUNC_Delegate | FUNC_MulticastDelegate)) + { + return false; + } + + if (!Function->HasAnyFunctionFlags(FUNC_Public)) + { + return false; + } + + if (Function->HasAnyFunctionFlags(FUNC_EditorOnly)) + { + return false; + } + + if (Function->HasMetaData(FName(TEXT("CustomThunk")))) + { + return false; + } + + if (const auto ReturnProperty = Function->GetReturnProperty()) + { + if (ReturnProperty->IsA()) + { + return false; + } + } + + for (TFieldIterator Property(Function); Property; ++Property) + { + if (!IsPropertyTypeSupported(*Property)) + { + return false; + } + } + + return true; +} + +bool FSourceCodeGeneratorModule::CanExportProperty(const FProperty* Property) +{ + return !Property->HasAnyPropertyFlags(CPF_EditorOnly) && + Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPublic) && + IsPropertyTypeSupported(Property); +} + +void FSourceCodeGeneratorModule::ExportFunction(FBigStringBuilder& StringBuilder, + const UClass* Class, + const UFunction* Function) +{ + StringBuilder.Appendf(TEXT( + "\t\t\t\t.Function(\"%s\", BINDING_OVERLOAD(%s, &%s::%s" + ), + *Function->GetName(), + *GetFunctionSignature(Class, Function), + *(Class->GetPrefixCPP() + Class->GetName()), + *Function->GetName()); + + auto bHasParameters = false; + + for (TFieldIterator Property(Function); Property; ++Property) + { + if (Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm)) + { + bHasParameters = true; + + break; + } + } + + if (bHasParameters) + { + StringBuilder.Appendf(TEXT( + ", %s" + ), + *GetFunctionParamName(Function)); + } + + if (!Function->HasAnyFunctionFlags(FUNC_Static)) + { + StringBuilder.Append(TEXT(", EFunctionInteract::Virtual")); + } + + if (bHasParameters) + { + StringBuilder.Append(GetFunctionDefaultValue(Function)); + } + + StringBuilder.Append(TEXT("))\r\n")); +} + +void FSourceCodeGeneratorModule::ExportProperty(FBigStringBuilder& StringBuilder, + const UClass* Class, + const FProperty* Property) +{ + StringBuilder.Appendf(TEXT( + "\t\t\t\t.Property(\"%s\", BINDING_PROPERTY(&%s::%s))\r\n" + ), + *Property->GetNameCPP(), + *(Class->GetPrefixCPP() + Class->GetName()), + *Property->GetNameCPP()); +} + +bool FSourceCodeGeneratorModule::IsClassTypeSupported(const UClass* Class) +{ + return !Class->HasAnyClassFlags(CLASS_Deprecated) && + (Class == UObject::StaticClass() || + Class == UClass::StaticClass() || + Class->HasAnyClassFlags(CLASS_RequiredAPI | CLASS_MinimalAPI)); +} + +bool FSourceCodeGeneratorModule::IsPropertyTypeSupported(const FProperty* Property) +{ + if (const auto ArrayProperty = CastField(Property)) + { + return IsPropertyTypeSupported(ArrayProperty->Inner); + } + + if (const auto SetProperty = CastField(Property)) + { + return IsPropertyTypeSupported(SetProperty->ElementProp); + } + + if (const auto MapProperty = CastField(Property)) + { + return IsPropertyTypeSupported(MapProperty->KeyProp) && + IsPropertyTypeSupported(MapProperty->ValueProp); + } + + if (const auto ObjectProperty = CastField(Property)) + { + if (!IsClassTypeSupported(ObjectProperty->PropertyClass)) + { + return false; + } + } + + if (const auto ObjectPtrProperty = CastField(Property)) + { + if (!IsClassTypeSupported(ObjectPtrProperty->PropertyClass)) + { + return false; + } + } + + if (const auto InterfaceProperty = CastField(Property)) + { + if (InterfaceProperty->InterfaceClass == UInterface::StaticClass()) + { + return false; + } + } + + if (Property->IsA()) + { + return false; + } + + if (Property->IsA()) + { + return false; + } + + if (Property->IsA()) + { + return false; + } + + if (Property->IsA()) + { + return false; + } + + if (Property->IsA()) + { + return false; + } + + if (const auto StructProperty = CastField(Property)) + { + if (!StructProperty->HasNoOpConstructor()) + { + return false; + } + } + + auto bIsBitField = false; + + if (const auto BoolProperty = CastField(Property)) + { + if (!BoolProperty->IsNativeBool()) + { + bIsBitField = true; + } + } + + return !bIsBitField && !(Property->ArrayDim > 1); +} + +FString FSourceCodeGeneratorModule::GetReturnPropertySignature(const FProperty* Property) +{ + if (Property == nullptr) + { + return TEXT("void"); + } + + FString String; + + auto bIsConst = false; + + if (Property->HasAnyPropertyFlags(CPF_ConstParm) || Property->HasMetaData(FName(TEXT("NativeConst")))) + { + String.Append(TEXT("const ")); + + bIsConst = true; + } + + GetPropertySignature(Property, String); + + if (Property->IsA()) + { + if (bIsConst && + !Property->HasAnyPropertyFlags(CPF_ConstParm)) + { + String.Append(TEXT("&")); + } + } + else if (Property->HasAnyPropertyFlags(CPF_ReferenceParm) && + Property->HasAnyPropertyFlags(CPF_ConstParm)) + { + String.Append(TEXT("&")); + } + + return String; +} + +FString FSourceCodeGeneratorModule::GetParamPropertySignature(const FProperty* Property) +{ + if (Property == nullptr) + { + return TEXT("void"); + } + + FString String; + + auto bIsConst = false; + + if (Property->HasAnyPropertyFlags(CPF_ConstParm) || Property->HasMetaData(FName(TEXT("NativeConst")))) + { + String.Append(TEXT("const ")); + + bIsConst = true; + } + + GetPropertySignature(Property, String); + + if (Property->IsA()) + { + if (bIsConst && + !Property->HasAnyPropertyFlags(CPF_ConstParm) || + Property->HasAnyPropertyFlags(CPF_OutParm)) + { + String.Append(TEXT("&")); + } + } + else if (Property->HasAnyPropertyFlags(CPF_OutParm) || + Property->HasAnyPropertyFlags(CPF_ReferenceParm)) + { + String.Append(TEXT("&")); + } + + return String; +} + +FString FSourceCodeGeneratorModule::GetFunctionSignature(const UClass* Class, const UFunction* Function) +{ + FString String; + + String.Append(GetReturnPropertySignature(Function->GetReturnProperty())); + + if (Function->HasAnyFunctionFlags(FUNC_Static)) + { + String.Append(TEXT("(*)")); + } + else + { + String.Appendf(TEXT( + "(%s::*)" + ), + *(Class->GetPrefixCPP() + Class->GetName()) + ); + } + + String.Append(TEXT("(")); + + auto bHasParameters = false; + + for (TFieldIterator Property(Function); Property; ++Property) + { + if (!Property->HasAnyPropertyFlags(CPF_ReturnParm)) + { + String.Append(GetParamPropertySignature(*Property)); + + String.Append(TEXT(", ")); + + bHasParameters = true; + } + } + + if (bHasParameters) + { + String.LeftInline(String.Len() - 2); + } + + String.Append(TEXT(")")); + + if (Function->HasAnyFunctionFlags(FUNC_Const)) + { + String.Append(TEXT("const")); + } + + return String; +} + +FString FSourceCodeGeneratorModule::GetFunctionParamName(const UFunction* Function) +{ + FSmallStringBuilder Builder; + + Builder.Append(TEXT("TArray{")); + + auto bIsConst = false; + + for (TFieldIterator Property(Function); Property; ++Property) + { + if (!Property->HasAnyPropertyFlags(CPF_ReturnParm)) + { + Builder.Appendf(TEXT( + "\"%s\", " + ), + *Property->GetName() + ); + + bIsConst = true; + } + } + + if (bIsConst) + { + Builder.RemoveSuffix(2); + } + + Builder.Append(TEXT("}")); + + return Builder.ToString(); +} + +FString FSourceCodeGeneratorModule::GetFunctionDefaultValue(const UFunction* Function) +{ + if (!Function->HasAnyFunctionFlags(FUNC_BlueprintCallable)) + { + return FString(); + } + + FSmallStringBuilder Builder; + + const auto MetaDataMap = UMetaData::GetMapForObject(Function); + + for (TFieldIterator Property(Function); Property; ++Property) + { + if (Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm)) + { + if (FString Value; FindDefaultValue(MetaDataMap, *Property, Value)) + { + Builder.Append(TEXT(", ")); + + if (Value == TEXT("NULL")) + { + Builder.Append(TEXT("nullptr")); + } + else + { + Builder.Append(Value); + } + } + } + } + + return Builder.ToString(); +} + +bool FSourceCodeGeneratorModule::FindDefaultValue(const TMap* MetaDataMap, const FProperty* Param, + FString& OutString) +{ + if (MetaDataMap == nullptr || Param == nullptr) + { + return false; + } + + const FName ParamName(*Param->GetName()); + + const FName CppKey(*(FString(TEXT("CPP_Default_")) + ParamName.ToString())); + + if (!MetaDataMap->Contains(CppKey)) + { + return false; + } + + if (Param->IsA() + || Param->IsA() + || Param->IsA()) + { + OutString = TEXT("nullptr"); + } + else + { + FString MetaData; + + if (const auto CppDefaultValue = MetaDataMap->Find(CppKey)) + { + if (!CppDefaultValue->IsEmpty()) + { + MetaData = *CppDefaultValue; + } + } + + if (const auto StructProperty = CastField(Param)) + { + if (StructProperty->Struct == TBaseStructure::Get()) + { + TArray Value; + + MetaData.ParseIntoArray(Value, TEXT(",")); + + if (Value.Num() == 3) + { + OutString = FString::Printf(TEXT( + "FVector(%lf, %lf, %lf)" + ), + TCString::Atod(*Value[0]), + TCString::Atod(*Value[1]), + TCString::Atod(*Value[2]) + ); + } + } + else if (StructProperty->Struct == TBaseStructure::Get()) + { + if (FVector2D Value; Value.InitFromString(MetaData)) + { + OutString = FString::Printf(TEXT( + "FVector2D(%lf, %lf)" + ), + Value.X, + Value.Y + ); + } + } + else if (StructProperty->Struct == TBaseStructure::Get()) + { + TArray Value; + + MetaData.ParseIntoArray(Value, TEXT(",")); + + if (Value.Num() == 3) + { + OutString = FString::Printf(TEXT( + "FRotator(%lf, %lf, %lf)" + ), + TCString::Atod(*Value[0]), + TCString::Atod(*Value[1]), + TCString::Atod(*Value[2]) + ); + } + } + else if (StructProperty->Struct == TBaseStructure::Get()) + { + if (FLinearColor Value; Value.InitFromString(MetaData)) + { + OutString = FString::Printf(TEXT( + "FLinearColor(%ff, %ff, %ff, %ff)" + ), + Value.R, + Value.G, + Value.B, + Value.A + ); + } + } + + if (OutString.IsEmpty()) + { + OutString = FString::Printf(TEXT( + "%s%s()" + ), + StructProperty->Struct->GetPrefixCPP(), + *StructProperty->Struct->GetName() + ); + } + } + else if (Param->IsA()) + { + OutString = FString::Printf(TEXT( + "FString(\"%s\")" + ), + *MetaData); + } + else if (Param->IsA()) + { + OutString = FString::Printf(TEXT( + "TEXT(\"%s\")" + ), + *MetaData); + } + else if (Param->IsA()) + { + static auto InvText = FString(TEXT("INVTEXT")); + + if (MetaData.Contains(InvText)) + { + MetaData.MidInline(InvText.Len() + 2, MetaData.Len() - InvText.Len() - 4); + } + + OutString = FString::Printf(TEXT( + "INVTEXT(\"%s\")" + ), + *MetaData + ); + } + else + { + OutString = MetaData; + } + } + + return !OutString.IsEmpty(); +} + +void FSourceCodeGeneratorModule::GetPropertySignature(const FProperty* Property, FString& String) +{ + if (const auto ArrayProperty = CastField(Property)) + { + String.Appendf(TEXT( + "TArray<%s>" + ), + *ArrayProperty->Inner->GetCPPType() + ); + } + else if (const auto SetProperty = CastField(Property)) + { + String.Appendf(TEXT( + "TSet<%s>" + ), + *SetProperty->ElementProp->GetCPPType() + ); + } + else if (const auto MapProperty = CastField(Property)) + { + String.Appendf(TEXT( + "TMap<%s, %s>" + ), + *MapProperty->KeyProp->GetCPPType(), + *MapProperty->ValueProp->GetCPPType() + ); + } + else if (const auto InterfaceProperty = CastField(Property)) + { + String.Appendf(TEXT( + "TScriptInterface" + ), + *InterfaceProperty->InterfaceClass->GetName() + ); + } + else + { + String.Append(Property->GetCPPType()); + } +} + +void FSourceCodeGeneratorModule::GetDependencyClasses(const FProperty* Property, TSet& DependencyClasses) +{ + if (const auto ClassProperty = CastField(Property)) + { + if (ClassProperty->MetaClass != nullptr) + { + DependencyClasses.Add(ClassProperty->MetaClass); + } + } + else if (const auto ObjectProperty = CastField(Property)) + { + if (ObjectProperty->PropertyClass != nullptr) + { + DependencyClasses.Add(ObjectProperty->PropertyClass); + } + } + else if (const auto ObjectPtrProperty = CastField(Property)) + { + if (ObjectPtrProperty->PropertyClass != nullptr) + { + DependencyClasses.Add(ObjectPtrProperty->PropertyClass); + } + } + else if (const auto SoftClassProperty = CastField(Property)) + { + if (SoftClassProperty->MetaClass != nullptr) + { + DependencyClasses.Add(SoftClassProperty->MetaClass); + } + } + else if (const auto SoftObjectProperty = CastField(Property)) + { + if (SoftObjectProperty->PropertyClass != nullptr) + { + DependencyClasses.Add(SoftObjectProperty->PropertyClass); + } + } + else if (const auto ArrayProperty = CastField(Property)) + { + if (ArrayProperty->Inner != nullptr) + { + GetDependencyClasses(ArrayProperty->Inner, DependencyClasses); + } + } +} + +void FSourceCodeGeneratorModule::SaveIfChanged(const FString& FileName, const FString& String) +{ + if (FPaths::FileExists(FileName)) + { + if (FString Result; FFileHelper::LoadFileToString(Result, *FileName) && Result == String) + { + return; + } + } + + FFileHelper::SaveStringToFile(String, *FileName, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); +} + +FString FSourceCodeGeneratorModule::GetHeaderFile(UClass* Class) +{ + FString ModuleRelativePath = Class->GetMetaData(TEXT("ModuleRelativePath")); + + if (ModuleRelativePath.IsEmpty()) + { + if (Class == UInterface::StaticClass()) + { + ModuleRelativePath = TEXT("Public/UObject/Interface.h"); + } + else if (Class == UPackageMap::StaticClass()) + { + ModuleRelativePath = TEXT("Public/UObject/CoreNet.h"); + } + } + + return FPaths::Combine(HeaderPath.FindRef(FPaths::GetCleanFilename(Class->GetPackage()->GetName())), + ModuleRelativePath); +} + +FString FSourceCodeGeneratorModule::GenerateInclude(const FString& FileName) +{ + return FString::Printf(TEXT( + "#include \"%s\"\n" + ), + *FileName + ); +} + +FString FSourceCodeGeneratorModule::GetInclude(UClass* Class) +{ + return GenerateInclude(GetHeaderFile(Class)); +} + +void FSourceCodeGeneratorModule::GetPlugins(const FString& InPathName, TMap& Plugins) +{ + const auto Suffix = FString(TEXT("*.uplugin")); + + if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName)) + { + TArray FileNames; + + FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false); + + for (const auto& FileName : FileNames) + { + Plugins.Add( + FPaths::GetCleanFilename(FileName).Left( + FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1), + FPaths::ConvertRelativePathToFull(FPaths::GetPath(FileName))); + } + } +} + +void FSourceCodeGeneratorModule::GetModules(const FString& InPathName, TMap& Modules) +{ + const auto Suffix = FString(TEXT("*.Build.cs")); + + if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName)) + { + TArray FileNames; + + FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false); + + for (const auto& FileName : FileNames) + { + Modules.Add( + FPaths::GetCleanFilename(FileName).Left( + FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1), + FPaths::ConvertRelativePathToFull(FPaths::GetPath(FileName))); + } + } +} + +void FSourceCodeGeneratorModule::GetModules(const FString& InPathName, TSet& Modules) +{ + const auto Suffix = FString(TEXT("*.Build.cs")); + + if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName)) + { + TArray FileNames; + + FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false); + + for (const auto& FileName : FileNames) + { + Modules.Add( + FPaths::GetCleanFilename(FileName).Left( + FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1)); + } + } +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FSourceCodeGeneratorModule, SourceCodeGenerator) diff --git a/Public/SourceCodeGenerator.h b/Public/SourceCodeGenerator.h new file mode 100644 index 0000000..eef0f2b --- /dev/null +++ b/Public/SourceCodeGenerator.h @@ -0,0 +1,101 @@ +#pragma once + +#include "CoreMinimal.h" +#include "IScriptGeneratorPluginInterface.h" + +class FSourceCodeGeneratorModule : public IScriptGeneratorPluginInterface +{ +private: + static constexpr auto LargeInitialBufferSizes = 256 * 1024; + + static constexpr auto SmallInitialBufferSizes = 1 * 1024; + + typedef TStringBuilder FBigStringBuilder; + + typedef TStringBuilder FSmallStringBuilder; + +public: + /** IModuleInterface implementation */ + virtual void StartupModule() override; + + virtual void ShutdownModule() override; + +public: + /** IScriptGeneratorPluginInterface implementation */ + virtual FString GetGeneratedCodeModuleName() const override; + + virtual bool SupportsTarget(const FString& TargetName) const override; + + virtual bool ShouldExportClassesForModule(const FString& ModuleName, EBuildModuleType::Type ModuleType, + const FString& ModuleGeneratedIncludeDirectory) const override; + + virtual void Initialize(const FString& RootLocalPath, const FString& RootBuildPath, + const FString& OutputDirectory, const FString& IncludeBase) override; + + virtual void ExportClass(UClass* Class, const FString& SourceHeaderFilename, + const FString& GeneratedHeaderFilename, bool bHasChanged) override; + + virtual void FinishExport() override; + + virtual FString GetGeneratorName() const override; + +private: + static bool CanExportClass(const UClass* Class); + + static bool CanExportFunction(const UFunction* Function); + + static bool CanExportProperty(const FProperty* Property); + + static void ExportFunction(FBigStringBuilder& StringBuilder, const UClass* Class, const UFunction* Function); + + static void ExportProperty(FBigStringBuilder& StringBuilder, const UClass* Class, const FProperty* Property); + + static bool IsClassTypeSupported(const UClass* Class); + + static bool IsPropertyTypeSupported(const FProperty* Property); + + static FString GetReturnPropertySignature(const FProperty* Property); + + static FString GetParamPropertySignature(const FProperty* Property); + + static FString GetFunctionSignature(const UClass* Class, const UFunction* Function); + + static FString GetFunctionParamName(const UFunction* Function); + + static FString GetFunctionDefaultValue(const UFunction* Function); + + static bool FindDefaultValue(const TMap* MetaMap, const FProperty* Param, FString& String); + + static void GetPropertySignature(const FProperty* Property, FString& String); + + static void GetDependencyClasses(const FProperty* Property, TSet& DependencyClasses); + + static void SaveIfChanged(const FString& FileName, const FString& String); + + FString GetHeaderFile(UClass* Class); + + static FString GenerateInclude(const FString& FileName); + + FString GetInclude(UClass* Class); + + static void GetPlugins(const FString& InPathName, TMap& Plugins); + + static void GetModules(const FString& InPathName, TMap& Modules); + + static void GetModules(const FString& InPathName, TSet& Modules); + +private: + FString OutputPath; + + TArray ExportModules; + + TSet Project; + + TMap HeaderPath; + + TArray ExportClasses; + + const FString BindingSuffix = FString(TEXT(".binding.inl")); + + const FString HeaderSuffix = FString(TEXT(".header.inl")); +}; diff --git a/SourceCodeGenerator.Build.cs b/SourceCodeGenerator.Build.cs new file mode 100644 index 0000000..626dc00 --- /dev/null +++ b/SourceCodeGenerator.Build.cs @@ -0,0 +1,40 @@ +using UnrealBuildTool; + +namespace UnrealBuildTool.Rules +{ + public class SourceCodeGenerator : ModuleRules + { + public SourceCodeGenerator(ReadOnlyTargetRules Target) : base(Target) + { + bEnforceIWYU = false; + + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] + { + "Programs/UnrealHeaderTool/Public", + } + ); + + + PrivateIncludePaths.AddRange( + new string[] + { + "SourceCodeGenerator/Private", + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + } + ); + + PublicDefinitions.Add("HACK_HEADER_GENERATOR=1"); + } + } +} \ No newline at end of file