2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
# include "GameProjectGenerationPrivatePCH.h"
2015-02-16 04:29:11 -05:00
# include "FeaturedClasses.inl"
2014-03-14 14:13:41 -04:00
# include "UnrealEdMisc.h"
# include "ISourceControlModule.h"
# include "MainFrame.h"
2014-08-11 17:14:26 -04:00
# include "DefaultTemplateProjectDefs.h"
2014-03-14 14:13:41 -04:00
# include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h"
# include "EngineAnalytics.h"
2014-04-02 18:09:23 -04:00
# include "EngineBuildSettings.h"
2014-03-14 14:13:41 -04:00
2014-04-23 18:32:52 -04:00
# include "DesktopPlatformModule.h"
2014-05-08 15:03:34 -04:00
# include "TargetPlatform.h"
2014-04-23 18:32:52 -04:00
2014-05-16 07:26:51 -04:00
# include "ClassIconFinder.h"
2014-12-12 12:30:06 -05:00
# include "Editor/UnrealEd/Public/SourceCodeNavigation.h"
2014-05-16 07:26:51 -04:00
2014-06-10 11:23:42 -04:00
# include "UProjectInfo.h"
2014-08-22 08:33:51 -04:00
# include "DesktopPlatformModule.h"
2014-10-14 22:50:06 -04:00
# include "SNotificationList.h"
# include "NotificationManager.h"
2014-12-01 19:04:12 -05:00
# include "GameFramework/GameMode.h"
2014-12-11 06:03:58 -05:00
# include "HotReloadInterface.h"
2014-12-11 16:39:39 -05:00
# include "SVerbChoiceDialog.h"
2015-01-08 09:29:27 -05:00
# include "SourceCodeNavigation.h"
2015-02-09 10:40:24 -05:00
///#include "AssetToolsModule.h"
2014-12-12 12:30:06 -05:00
2015-04-27 15:33:53 -04:00
# include "SOutputLogDialog.h"
2014-03-14 14:13:41 -04:00
# define LOCTEXT_NAMESPACE "GameProjectUtils"
# define MAX_PROJECT_PATH_BUFFER_SPACE 130 // Leave a reasonable buffer of additional characters to account for files created in the content directory during or after project generation
2014-04-24 10:58:49 -04:00
# define MAX_PROJECT_NAME_LENGTH 20 // Enforce a reasonable project name length so the path is not too long for PLATFORM_MAX_FILEPATH_LENGTH
2014-06-16 08:04:54 -04:00
static_assert ( PLATFORM_MAX_FILEPATH_LENGTH - MAX_PROJECT_PATH_BUFFER_SPACE > 0 , " File system path shorter than project creation buffer space. " ) ;
2014-03-14 14:13:41 -04:00
# define MAX_CLASS_NAME_LENGTH 32 // Enforce a reasonable class name length so the path is not too long for PLATFORM_MAX_FILEPATH_LENGTH
TWeakPtr < SNotificationItem > GameProjectUtils : : UpdateGameProjectNotification = NULL ;
2014-05-01 10:58:35 -04:00
TWeakPtr < SNotificationItem > GameProjectUtils : : WarningProjectNameNotification = NULL ;
2014-03-14 14:13:41 -04:00
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetClassName ( ) const
2014-05-16 07:26:51 -04:00
{
switch ( ClassType )
{
case EClassType : : UObject :
return BaseClass ? FName : : NameToDisplayString ( BaseClass - > GetName ( ) , false ) : TEXT ( " " ) ;
case EClassType : : EmptyCpp :
return TEXT ( " None " ) ;
case EClassType : : SlateWidget :
return TEXT ( " Slate Widget " ) ;
case EClassType : : SlateWidgetStyle :
return TEXT ( " Slate Widget Style " ) ;
default :
break ;
}
return TEXT ( " " ) ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetClassDescription ( ) const
2014-05-16 07:26:51 -04:00
{
switch ( ClassType )
{
case EClassType : : UObject :
{
if ( BaseClass )
{
FString ClassDescription = BaseClass - > GetToolTipText ( ) . ToString ( ) ;
int32 FullStopIndex = 0 ;
if ( ClassDescription . FindChar ( ' . ' , FullStopIndex ) )
{
// Only show the first sentence so as not to clutter up the UI with a detailed description of implementation details
ClassDescription = ClassDescription . Left ( FullStopIndex + 1 ) ;
}
// Strip out any new-lines in the description
ClassDescription = ClassDescription . Replace ( TEXT ( " \n " ) , TEXT ( " " ) ) ;
return ClassDescription ;
}
}
break ;
case EClassType : : EmptyCpp :
return TEXT ( " An empty C++ class with a default constructor and destructor " ) ;
case EClassType : : SlateWidget :
return TEXT ( " A custom Slate widget, deriving from SCompoundWidget " ) ;
case EClassType : : SlateWidgetStyle :
return TEXT ( " A custom Slate widget style, deriving from FSlateWidgetStyle, along with its associated UObject wrapper class " ) ;
default :
break ;
}
return TEXT ( " " ) ;
}
2015-02-16 04:29:11 -05:00
const FSlateBrush * FNewClassInfo : : GetClassIcon ( ) const
2014-05-16 07:26:51 -04:00
{
// Safe to do even if BaseClass is null, since FindIconForClass will return the default icon
return FClassIconFinder : : FindIconForClass ( BaseClass ) ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetClassPrefixCPP ( ) const
2014-05-16 07:26:51 -04:00
{
switch ( ClassType )
{
case EClassType : : UObject :
return BaseClass ? BaseClass - > GetPrefixCPP ( ) : TEXT ( " " ) ;
case EClassType : : EmptyCpp :
return TEXT ( " " ) ;
case EClassType : : SlateWidget :
return TEXT ( " S " ) ;
case EClassType : : SlateWidgetStyle :
return TEXT ( " F " ) ;
default :
break ;
}
return TEXT ( " " ) ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetClassNameCPP ( ) const
2014-05-16 07:26:51 -04:00
{
switch ( ClassType )
{
case EClassType : : UObject :
return BaseClass ? BaseClass - > GetName ( ) : TEXT ( " " ) ;
case EClassType : : EmptyCpp :
return TEXT ( " " ) ;
case EClassType : : SlateWidget :
return TEXT ( " CompoundWidget " ) ;
case EClassType : : SlateWidgetStyle :
return TEXT ( " SlateWidgetStyle " ) ;
default :
break ;
}
return TEXT ( " " ) ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetCleanClassName ( const FString & ClassName ) const
2014-05-16 07:26:51 -04:00
{
FString CleanClassName = ClassName ;
switch ( ClassType )
{
case EClassType : : SlateWidgetStyle :
{
// Slate widget style classes always take the form FMyThingWidget, and UMyThingWidgetStyle
// if our class ends with either Widget or WidgetStyle, we need to strip those out to avoid silly looking duplicates
if ( CleanClassName . EndsWith ( TEXT ( " Style " ) ) )
{
CleanClassName = CleanClassName . LeftChop ( 5 ) ; // 5 for "Style"
}
if ( CleanClassName . EndsWith ( TEXT ( " Widget " ) ) )
{
CleanClassName = CleanClassName . LeftChop ( 6 ) ; // 6 for "Widget"
}
}
break ;
default :
break ;
}
return CleanClassName ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetFinalClassName ( const FString & ClassName ) const
2014-05-16 07:26:51 -04:00
{
const FString CleanClassName = GetCleanClassName ( ClassName ) ;
switch ( ClassType )
{
case EClassType : : SlateWidgetStyle :
return FString : : Printf ( TEXT ( " %sWidgetStyle " ) , * CleanClassName ) ;
default :
break ;
}
return CleanClassName ;
}
2015-02-16 04:29:11 -05:00
bool FNewClassInfo : : GetIncludePath ( FString & OutIncludePath ) const
2014-05-16 07:26:51 -04:00
{
switch ( ClassType )
{
case EClassType : : UObject :
if ( BaseClass & & BaseClass - > HasMetaData ( TEXT ( " IncludePath " ) ) )
{
OutIncludePath = BaseClass - > GetMetaData ( TEXT ( " IncludePath " ) ) ;
return true ;
}
break ;
2015-03-17 09:34:18 -04:00
case EClassType : : SlateWidget :
OutIncludePath = " Widgets/SCompoundWidget.h " ;
return true ;
case EClassType : : SlateWidgetStyle :
OutIncludePath = " Styling/SlateWidgetStyle.h " ;
return true ;
2014-05-16 07:26:51 -04:00
default :
break ;
}
return false ;
}
2015-03-17 09:34:18 -04:00
FString FNewClassInfo : : GetBaseClassHeaderFilename ( ) const
{
FString IncludePath ;
switch ( ClassType )
{
case EClassType : : UObject :
if ( BaseClass )
{
FString ClassHeaderPath ;
if ( FSourceCodeNavigation : : FindClassHeaderPath ( BaseClass , ClassHeaderPath ) & & IFileManager : : Get ( ) . FileSize ( * ClassHeaderPath ) ! = INDEX_NONE )
{
return ClassHeaderPath ;
}
}
break ;
case EClassType : : SlateWidget :
case EClassType : : SlateWidgetStyle :
GetIncludePath ( IncludePath ) ;
return FPaths : : EngineDir ( ) / TEXT ( " Source " ) / TEXT ( " Runtime " ) / TEXT ( " SlateCore " ) / TEXT ( " Public " ) / IncludePath ;
default :
return FString ( ) ;
}
return FString ( ) ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetHeaderFilename ( const FString & ClassName ) const
2014-05-16 07:26:51 -04:00
{
const FString HeaderFilename = GetFinalClassName ( ClassName ) + TEXT ( " .h " ) ;
switch ( ClassType )
{
case EClassType : : SlateWidget :
return TEXT ( " S " ) + HeaderFilename ;
default :
break ;
}
return HeaderFilename ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetSourceFilename ( const FString & ClassName ) const
2014-05-16 07:26:51 -04:00
{
const FString SourceFilename = GetFinalClassName ( ClassName ) + TEXT ( " .cpp " ) ;
switch ( ClassType )
{
case EClassType : : SlateWidget :
return TEXT ( " S " ) + SourceFilename ;
default :
break ;
}
return SourceFilename ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetHeaderTemplateFilename ( ) const
2014-05-16 07:26:51 -04:00
{
switch ( ClassType )
{
2015-01-26 20:01:46 -05:00
case EClassType : : UObject :
{
2015-02-18 05:00:44 -05:00
if ( BaseClass ! = nullptr )
2015-01-26 20:01:46 -05:00
{
2015-02-18 05:00:44 -05:00
if ( ( BaseClass = = UActorComponent : : StaticClass ( ) ) | | ( BaseClass = = USceneComponent : : StaticClass ( ) ) )
{
return TEXT ( " ActorComponentClass.h.template " ) ;
}
else if ( BaseClass = = AActor : : StaticClass ( ) )
{
return TEXT ( " ActorClass.h.template " ) ;
}
else if ( BaseClass = = APawn : : StaticClass ( ) )
{
return TEXT ( " PawnClass.h.template " ) ;
}
else if ( BaseClass = = ACharacter : : StaticClass ( ) )
{
return TEXT ( " CharacterClass.h.template " ) ;
}
2015-01-26 20:01:46 -05:00
}
2015-02-18 05:00:44 -05:00
// Some other non-actor, non-component UObject class
return TEXT ( " UObjectClass.h.template " ) ;
2015-01-26 20:01:46 -05:00
}
2014-05-16 07:26:51 -04:00
case EClassType : : EmptyCpp :
return TEXT ( " EmptyClass.h.template " ) ;
case EClassType : : SlateWidget :
return TEXT ( " SlateWidget.h.template " ) ;
case EClassType : : SlateWidgetStyle :
return TEXT ( " SlateWidgetStyle.h.template " ) ;
default :
break ;
}
return TEXT ( " " ) ;
}
2015-02-16 04:29:11 -05:00
FString FNewClassInfo : : GetSourceTemplateFilename ( ) const
2014-05-16 07:26:51 -04:00
{
switch ( ClassType )
{
2015-01-26 20:01:46 -05:00
case EClassType : : UObject :
2015-02-18 05:00:44 -05:00
if ( BaseClass ! = nullptr )
2015-01-26 20:01:46 -05:00
{
2015-02-18 05:00:44 -05:00
if ( ( BaseClass = = UActorComponent : : StaticClass ( ) ) | | ( BaseClass = = USceneComponent : : StaticClass ( ) ) )
{
return TEXT ( " ActorComponentClass.cpp.template " ) ;
}
else if ( BaseClass = = AActor : : StaticClass ( ) )
{
return TEXT ( " ActorClass.cpp.template " ) ;
}
else if ( BaseClass = = APawn : : StaticClass ( ) )
{
return TEXT ( " PawnClass.cpp.template " ) ;
}
else if ( BaseClass = = ACharacter : : StaticClass ( ) )
{
return TEXT ( " CharacterClass.cpp.template " ) ;
}
2015-01-26 20:01:46 -05:00
}
2015-02-18 05:00:44 -05:00
// Some other non-actor, non-component UObject class
return TEXT ( " UObjectClass.cpp.template " ) ;
2015-01-26 20:01:46 -05:00
2014-05-16 07:26:51 -04:00
case EClassType : : EmptyCpp :
return TEXT ( " EmptyClass.cpp.template " ) ;
case EClassType : : SlateWidget :
return TEXT ( " SlateWidget.cpp.template " ) ;
case EClassType : : SlateWidgetStyle :
return TEXT ( " SlateWidgetStyle.cpp.template " ) ;
default :
break ;
}
return TEXT ( " " ) ;
}
2014-03-14 14:13:41 -04:00
bool GameProjectUtils : : IsValidProjectFileForCreation ( const FString & ProjectFile , FText & OutFailReason )
{
const FString BaseProjectFile = FPaths : : GetBaseFilename ( ProjectFile ) ;
if ( FPaths : : GetPath ( ProjectFile ) . IsEmpty ( ) )
{
OutFailReason = LOCTEXT ( " NoProjectPath " , " You must specify a path. " ) ;
return false ;
}
if ( BaseProjectFile . IsEmpty ( ) )
{
OutFailReason = LOCTEXT ( " NoProjectName " , " You must specify a project name. " ) ;
return false ;
}
if ( BaseProjectFile . Contains ( TEXT ( " " ) ) )
{
OutFailReason = LOCTEXT ( " ProjectNameContainsSpace " , " Project names may not contain a space. " ) ;
return false ;
}
if ( ! FChar : : IsAlpha ( BaseProjectFile [ 0 ] ) )
{
OutFailReason = LOCTEXT ( " ProjectNameMustBeginWithACharacter " , " Project names must begin with an alphabetic character. " ) ;
return false ;
}
if ( BaseProjectFile . Len ( ) > MAX_PROJECT_NAME_LENGTH )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " MaxProjectNameLength " ) , MAX_PROJECT_NAME_LENGTH ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectNameTooLong " , " Project names must not be longer than {MaxProjectNameLength} characters. " ) , Args ) ;
return false ;
}
const int32 MaxProjectPathLength = PLATFORM_MAX_FILEPATH_LENGTH - MAX_PROJECT_PATH_BUFFER_SPACE ;
if ( FPaths : : GetBaseFilename ( ProjectFile , false ) . Len ( ) > MaxProjectPathLength )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " MaxProjectPathLength " ) , MaxProjectPathLength ) ;
2014-09-09 12:16:36 -04:00
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectPathTooLong " , " A project's path must not be longer than {MaxProjectPathLength} characters. " ) , Args ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
2014-09-08 13:51:36 -04:00
if ( FPaths : : GetExtension ( ProjectFile ) ! = FProjectDescriptor : : GetExtension ( ) )
2014-03-14 14:13:41 -04:00
{
FFormatNamedArguments Args ;
2014-09-08 13:51:36 -04:00
Args . Add ( TEXT ( " ProjectFileExtension " ) , FText : : FromString ( FProjectDescriptor : : GetExtension ( ) ) ) ;
2014-03-14 14:13:41 -04:00
OutFailReason = FText : : Format ( LOCTEXT ( " InvalidProjectFileExtension " , " File extension is not {ProjectFileExtension} " ) , Args ) ;
return false ;
}
FString IllegalNameCharacters ;
if ( ! NameContainsOnlyLegalCharacters ( BaseProjectFile , IllegalNameCharacters ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " IllegalNameCharacters " ) , FText : : FromString ( IllegalNameCharacters ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectNameContainsIllegalCharacters " , " Project names may not contain the following characters: {IllegalNameCharacters} " ) , Args ) ;
return false ;
}
2014-05-08 15:03:34 -04:00
if ( NameContainsUnderscoreAndXB1Installed ( BaseProjectFile ) )
{
OutFailReason = LOCTEXT ( " ProjectNameContainsIllegalCharactersOnXB1 " , " Project names may not contain an underscore when the Xbox One XDK is installed. " ) ;
return false ;
}
2014-04-23 18:13:53 -04:00
if ( ! FPaths : : ValidatePath ( FPaths : : GetPath ( ProjectFile ) , & OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
return false ;
}
if ( ProjectFileExists ( ProjectFile ) )
{
2014-09-09 12:16:36 -04:00
OutFailReason = LOCTEXT ( " ProjectFileAlreadyExists " , " This project file already exists. " ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
if ( FPaths : : ConvertRelativePathToFull ( FPaths : : GetPath ( ProjectFile ) ) . StartsWith ( FPaths : : ConvertRelativePathToFull ( FPaths : : EngineDir ( ) ) ) )
{
2014-09-09 12:16:36 -04:00
OutFailReason = LOCTEXT ( " ProjectFileCannotBeUnderEngineFolder " , " Project cannot be saved under the Engine folder. Please choose a different directory. " ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
if ( AnyProjectFilesExistInFolder ( FPaths : : GetPath ( ProjectFile ) ) )
{
FFormatNamedArguments Args ;
2014-09-08 13:51:36 -04:00
Args . Add ( TEXT ( " ProjectFileExtension " ) , FText : : FromString ( FProjectDescriptor : : GetExtension ( ) ) ) ;
2014-09-09 12:16:36 -04:00
OutFailReason = FText : : Format ( LOCTEXT ( " AProjectFileAlreadyExistsAtLoction " , " Another .{ProjectFileExtension} file already exists in the specified folder " ) , Args ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
return true ;
}
bool GameProjectUtils : : OpenProject ( const FString & ProjectFile , FText & OutFailReason )
{
if ( ProjectFile . IsEmpty ( ) )
{
OutFailReason = LOCTEXT ( " NoProjectFileSpecified " , " You must specify a project file. " ) ;
return false ;
}
const FString BaseProjectFile = FPaths : : GetBaseFilename ( ProjectFile ) ;
if ( BaseProjectFile . Contains ( TEXT ( " " ) ) )
{
OutFailReason = LOCTEXT ( " ProjectNameContainsSpace " , " Project names may not contain a space. " ) ;
return false ;
}
if ( ! FChar : : IsAlpha ( BaseProjectFile [ 0 ] ) )
{
OutFailReason = LOCTEXT ( " ProjectNameMustBeginWithACharacter " , " Project names must begin with an alphabetic character. " ) ;
return false ;
}
const int32 MaxProjectPathLength = PLATFORM_MAX_FILEPATH_LENGTH - MAX_PROJECT_PATH_BUFFER_SPACE ;
if ( FPaths : : GetBaseFilename ( ProjectFile , false ) . Len ( ) > MaxProjectPathLength )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " MaxProjectPathLength " ) , MaxProjectPathLength ) ;
2014-09-09 12:16:36 -04:00
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectPathTooLong " , " A project's path must not be longer than {MaxProjectPathLength} characters. " ) , Args ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
2014-09-08 13:51:36 -04:00
if ( FPaths : : GetExtension ( ProjectFile ) ! = FProjectDescriptor : : GetExtension ( ) )
2014-03-14 14:13:41 -04:00
{
FFormatNamedArguments Args ;
2014-09-08 13:51:36 -04:00
Args . Add ( TEXT ( " ProjectFileExtension " ) , FText : : FromString ( FProjectDescriptor : : GetExtension ( ) ) ) ;
2014-03-14 14:13:41 -04:00
OutFailReason = FText : : Format ( LOCTEXT ( " InvalidProjectFileExtension " , " File extension is not {ProjectFileExtension} " ) , Args ) ;
return false ;
}
FString IllegalNameCharacters ;
if ( ! NameContainsOnlyLegalCharacters ( BaseProjectFile , IllegalNameCharacters ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " IllegalNameCharacters " ) , FText : : FromString ( IllegalNameCharacters ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectNameContainsIllegalCharacters " , " Project names may not contain the following characters: {IllegalNameCharacters} " ) , Args ) ;
return false ;
}
2014-05-08 15:03:34 -04:00
if ( NameContainsUnderscoreAndXB1Installed ( BaseProjectFile ) )
{
OutFailReason = LOCTEXT ( " ProjectNameContainsIllegalCharactersOnXB1 " , " Project names may not contain an underscore when the Xbox One XDK is installed. " ) ;
return false ;
}
2014-04-23 18:13:53 -04:00
if ( ! FPaths : : ValidatePath ( FPaths : : GetPath ( ProjectFile ) , & OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
return false ;
}
if ( ! ProjectFileExists ( ProjectFile ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ProjectFile " ) , FText : : FromString ( ProjectFile ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectFileDoesNotExist " , " {ProjectFile} does not exist. " ) , Args ) ;
return false ;
}
FUnrealEdMisc : : Get ( ) . SwitchProject ( ProjectFile , false ) ;
return true ;
}
bool GameProjectUtils : : OpenCodeIDE ( const FString & ProjectFile , FText & OutFailReason )
{
if ( ProjectFile . IsEmpty ( ) )
{
OutFailReason = LOCTEXT ( " NoProjectFileSpecified " , " You must specify a project file. " ) ;
return false ;
}
2014-06-10 11:23:42 -04:00
// Check whether this project is a foreign project. Don't use the cached project dictionary; we may have just created a new project.
2014-03-14 14:13:41 -04:00
FString SolutionFolder ;
FString SolutionFilenameWithoutExtension ;
2014-06-23 15:36:37 -04:00
if ( FUProjectDictionary ( FPaths : : RootDir ( ) ) . IsForeignProject ( ProjectFile ) )
2014-03-14 14:13:41 -04:00
{
SolutionFolder = IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForRead ( * FPaths : : GetPath ( ProjectFile ) ) ;
SolutionFilenameWithoutExtension = FPaths : : GetBaseFilename ( ProjectFile ) ;
}
2014-06-23 15:36:37 -04:00
else
{
SolutionFolder = IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForRead ( * FPaths : : RootDir ( ) ) ;
SolutionFilenameWithoutExtension = TEXT ( " UE4 " ) ;
}
2014-03-14 14:13:41 -04:00
2014-06-23 15:36:37 -04:00
// Get the solution filename
2014-03-14 14:13:41 -04:00
FString CodeSolutionFile ;
# if PLATFORM_WINDOWS
CodeSolutionFile = SolutionFilenameWithoutExtension + TEXT ( " .sln " ) ;
# elif PLATFORM_MAC
CodeSolutionFile = SolutionFilenameWithoutExtension + TEXT ( " .xcodeproj " ) ;
2014-07-29 01:53:30 -04:00
# elif PLATFORM_LINUX
2014-11-04 16:37:36 -05:00
// FIXME: need a better way to select between plugins. For now we don't generate .kdev4 directly. Should depend on PreferredAccessor setting
CodeSolutionFile = SolutionFilenameWithoutExtension + TEXT ( " .pro " ) ;
2014-03-14 14:13:41 -04:00
# else
2014-06-05 17:11:45 -04:00
OutFailReason = LOCTEXT ( " OpenCodeIDE_UnknownPlatform " , " could not open the code editing IDE. The operating system is unknown. " ) ;
2014-03-14 14:13:41 -04:00
return false ;
# endif
2014-06-23 15:36:37 -04:00
// Open the solution with the default application
2014-03-14 14:13:41 -04:00
const FString FullPath = FPaths : : Combine ( * SolutionFolder , * CodeSolutionFile ) ;
2014-07-29 01:53:30 -04:00
# if PLATFORM_MAC
2014-03-14 14:13:41 -04:00
if ( IFileManager : : Get ( ) . DirectoryExists ( * FullPath ) )
# else
if ( FPaths : : FileExists ( FullPath ) )
# endif
{
FPlatformProcess : : LaunchFileInDefaultExternalApplication ( * FullPath ) ;
return true ;
}
else
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " Path " ) , FText : : FromString ( FullPath ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " OpenCodeIDE_MissingFile " , " Could not edit the code editing IDE. {Path} could not be found. " ) , Args ) ;
return false ;
}
}
void GameProjectUtils : : GetStarterContentFiles ( TArray < FString > & OutFilenames )
{
2015-02-09 10:40:24 -05:00
FString const SrcFolder = FPaths : : FeaturePackDir ( ) ;
IFileManager : : Get ( ) . FindFilesRecursive ( OutFilenames , * SrcFolder , TEXT ( " *.upack " ) , /*Files=*/ true , /*Directories=*/ false ) ;
2014-03-14 14:13:41 -04:00
}
2015-04-09 11:11:48 -04:00
bool GameProjectUtils : : CreateProject ( const FProjectInformation & InProjectInfo , FText & OutFailReason , FText & OutFailLog )
2014-03-14 14:13:41 -04:00
{
2014-09-09 12:16:36 -04:00
if ( ! IsValidProjectFileForCreation ( InProjectInfo . ProjectFilename , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
return false ;
}
2014-10-08 04:42:34 -04:00
FScopedSlowTask SlowTask ( 0 , LOCTEXT ( " CreatingProjectStatus " , " Creating project... " ) ) ;
SlowTask . MakeDialog ( ) ;
2014-03-14 14:13:41 -04:00
bool bProjectCreationSuccessful = false ;
FString TemplateName ;
2014-09-09 12:16:36 -04:00
if ( InProjectInfo . TemplateFile . IsEmpty ( ) )
2014-03-14 14:13:41 -04:00
{
2015-04-09 11:11:48 -04:00
bProjectCreationSuccessful = GenerateProjectFromScratch ( InProjectInfo , OutFailReason , OutFailLog ) ;
2014-09-09 12:16:36 -04:00
TemplateName = InProjectInfo . bShouldGenerateCode ? TEXT ( " Basic Code " ) : TEXT ( " Blank " ) ;
2014-03-14 14:13:41 -04:00
}
else
{
2015-04-09 11:11:48 -04:00
bProjectCreationSuccessful = CreateProjectFromTemplate ( InProjectInfo , OutFailReason , OutFailLog ) ;
2014-09-09 12:16:36 -04:00
TemplateName = FPaths : : GetBaseFilename ( InProjectInfo . TemplateFile ) ;
2014-03-14 14:13:41 -04:00
}
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > EventAttributes ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " Template " ) , TemplateName ) ) ;
2014-09-09 12:16:36 -04:00
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " ProjectType " ) , InProjectInfo . bShouldGenerateCode ? TEXT ( " C++ Code " ) : TEXT ( " Content Only " ) ) ) ;
2014-03-14 14:13:41 -04:00
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " Outcome " ) , bProjectCreationSuccessful ? TEXT ( " Successful " ) : TEXT ( " Failed " ) ) ) ;
2014-10-03 05:17:13 -04:00
UEnum * Enum = FindObject < UEnum > ( ANY_PACKAGE , TEXT ( " EHardwareClass " ) , true ) ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " HardwareClass " ) , Enum ? Enum - > GetEnumName ( InProjectInfo . TargetedHardware ) : FString ( ) ) ) ;
Enum = FindObject < UEnum > ( ANY_PACKAGE , TEXT ( " EGraphicsPreset " ) , true ) ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " GraphicsPreset " ) , Enum ? Enum - > GetEnumName ( InProjectInfo . DefaultGraphicsPerformance ) : FString ( ) ) ) ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " StarterContent " ) , InProjectInfo . bCopyStarterContent ? TEXT ( " Yes " ) : TEXT ( " No " ) ) ) ;
2014-03-14 14:13:41 -04:00
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Editor.NewProject.ProjectCreated " ) , EventAttributes ) ;
}
return bProjectCreationSuccessful ;
}
void GameProjectUtils : : CheckForOutOfDateGameProjectFile ( )
{
2014-05-29 17:37:19 -04:00
if ( FPaths : : IsProjectFilePathSet ( ) )
2014-03-14 14:13:41 -04:00
{
FProjectStatus ProjectStatus ;
2014-05-29 17:37:19 -04:00
if ( IProjectManager : : Get ( ) . QueryStatusForCurrentProject ( ProjectStatus ) )
2014-03-14 14:13:41 -04:00
{
2014-05-13 18:23:53 -04:00
if ( ProjectStatus . bRequiresUpdate )
2014-03-14 14:13:41 -04:00
{
2014-05-13 18:23:53 -04:00
const FText UpdateProjectText = LOCTEXT ( " UpdateProjectFilePrompt " , " Project file is saved in an older format. Would you like to update it? " ) ;
2014-04-23 18:07:55 -04:00
const FText UpdateProjectConfirmText = LOCTEXT ( " UpdateProjectFileConfirm " , " Update " ) ;
const FText UpdateProjectCancelText = LOCTEXT ( " UpdateProjectFileCancel " , " Not Now " ) ;
FNotificationInfo Info ( UpdateProjectText ) ;
Info . bFireAndForget = false ;
Info . bUseLargeFont = false ;
Info . bUseThrobber = false ;
Info . bUseSuccessFailIcons = false ;
Info . FadeOutDuration = 3.f ;
Info . ButtonDetails . Add ( FNotificationButtonInfo ( UpdateProjectConfirmText , FText ( ) , FSimpleDelegate : : CreateStatic ( & GameProjectUtils : : OnUpdateProjectConfirm ) ) ) ;
Info . ButtonDetails . Add ( FNotificationButtonInfo ( UpdateProjectCancelText , FText ( ) , FSimpleDelegate : : CreateStatic ( & GameProjectUtils : : OnUpdateProjectCancel ) ) ) ;
if ( UpdateGameProjectNotification . IsValid ( ) )
2014-03-14 14:13:41 -04:00
{
2014-04-23 18:07:55 -04:00
UpdateGameProjectNotification . Pin ( ) - > ExpireAndFadeout ( ) ;
UpdateGameProjectNotification . Reset ( ) ;
2014-03-14 14:13:41 -04:00
}
2014-04-23 18:07:55 -04:00
UpdateGameProjectNotification = FSlateNotificationManager : : Get ( ) . AddNotification ( Info ) ;
if ( UpdateGameProjectNotification . IsValid ( ) )
2014-03-14 14:13:41 -04:00
{
2014-04-23 18:07:55 -04:00
UpdateGameProjectNotification . Pin ( ) - > SetCompletionState ( SNotificationItem : : CS_Pending ) ;
2014-03-14 14:13:41 -04:00
}
}
}
}
}
2014-05-01 10:58:35 -04:00
void GameProjectUtils : : CheckAndWarnProjectFilenameValid ( )
{
const FString & LoadedProjectFilePath = FPaths : : IsProjectFilePathSet ( ) ? FPaths : : GetProjectFilePath ( ) : FString ( ) ;
if ( ! LoadedProjectFilePath . IsEmpty ( ) )
{
const FString BaseProjectFile = FPaths : : GetBaseFilename ( LoadedProjectFilePath ) ;
if ( BaseProjectFile . Len ( ) > MAX_PROJECT_NAME_LENGTH )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " MaxProjectNameLength " ) , MAX_PROJECT_NAME_LENGTH ) ;
const FText WarningReason = FText : : Format ( LOCTEXT ( " WarnProjectNameTooLong " , " Project names must not be longer than {MaxProjectNameLength} characters. \n You might have problems saving or modifying a project with a longer name. " ) , Args ) ;
const FText WarningReasonOkText = LOCTEXT ( " WarningReasonOkText " , " Ok " ) ;
FNotificationInfo Info ( WarningReason ) ;
Info . bFireAndForget = false ;
Info . bUseLargeFont = false ;
Info . bUseThrobber = false ;
Info . bUseSuccessFailIcons = false ;
Info . FadeOutDuration = 3.f ;
Info . ButtonDetails . Add ( FNotificationButtonInfo ( WarningReasonOkText , FText ( ) , FSimpleDelegate : : CreateStatic ( & GameProjectUtils : : OnWarningReasonOk ) ) ) ;
if ( WarningProjectNameNotification . IsValid ( ) )
{
WarningProjectNameNotification . Pin ( ) - > ExpireAndFadeout ( ) ;
WarningProjectNameNotification . Reset ( ) ;
}
WarningProjectNameNotification = FSlateNotificationManager : : Get ( ) . AddNotification ( Info ) ;
if ( WarningProjectNameNotification . IsValid ( ) )
{
WarningProjectNameNotification . Pin ( ) - > SetCompletionState ( SNotificationItem : : CS_Pending ) ;
}
}
}
}
void GameProjectUtils : : OnWarningReasonOk ( )
{
if ( WarningProjectNameNotification . IsValid ( ) )
{
WarningProjectNameNotification . Pin ( ) - > SetCompletionState ( SNotificationItem : : CS_None ) ;
WarningProjectNameNotification . Pin ( ) - > ExpireAndFadeout ( ) ;
WarningProjectNameNotification . Reset ( ) ;
}
}
2015-03-17 09:34:18 -04:00
bool GameProjectUtils : : UpdateStartupModuleNames ( FProjectDescriptor & Descriptor , const TArray < FString > * StartupModuleNames )
{
if ( StartupModuleNames = = nullptr )
{
return false ;
}
// Replace the modules names, if specified
Descriptor . Modules . Empty ( ) ;
for ( int32 Idx = 0 ; Idx < StartupModuleNames - > Num ( ) ; Idx + + )
{
Descriptor . Modules . Add ( FModuleDescriptor ( * ( * StartupModuleNames ) [ Idx ] ) ) ;
}
return true ;
}
bool GameProjectUtils : : UpdateRequiredAdditionalDependencies ( FProjectDescriptor & Descriptor , TArray < FString > & RequiredDependencies , const FString & ModuleName )
{
bool bNeedsUpdate = false ;
for ( auto & ModuleDesc : Descriptor . Modules )
{
if ( ModuleDesc . Name ! = * ModuleName )
{
continue ;
}
for ( const auto & RequiredDep : RequiredDependencies )
{
if ( ! ModuleDesc . AdditionalDependencies . Contains ( RequiredDep ) )
{
ModuleDesc . AdditionalDependencies . Add ( RequiredDep ) ;
bNeedsUpdate = true ;
}
}
}
return bNeedsUpdate ;
}
2014-07-22 15:58:02 -04:00
bool GameProjectUtils : : UpdateGameProject ( const FString & ProjectFile , const FString & EngineIdentifier , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
2015-03-17 09:34:18 -04:00
return UpdateGameProjectFile ( ProjectFile , EngineIdentifier , OutFailReason ) ;
2014-03-14 14:13:41 -04:00
}
2015-02-16 04:29:11 -05:00
void GameProjectUtils : : OpenAddToProjectDialog ( const FAddToProjectConfig & Config , EClassDomain InDomain )
2014-03-14 14:13:41 -04:00
{
2015-01-16 15:39:47 -05:00
// If we've been given a class then we only show the second page of the dialog, so we can make the window smaller as that page doesn't have as much content
2015-02-16 12:49:48 -05:00
const FVector2D WindowSize = ( Config . _ParentClass ) ? ( InDomain = = EClassDomain : : Blueprint ) ? FVector2D ( 940 , 480 ) : FVector2D ( 940 , 380 ) : FVector2D ( 940 , 540 ) ;
2015-02-16 04:29:11 -05:00
FText WindowTitle = Config . _WindowTitle ;
if ( WindowTitle . IsEmpty ( ) )
{
2015-02-18 12:30:56 -05:00
WindowTitle = InDomain = = EClassDomain : : Native ? LOCTEXT ( " AddCodeWindowHeader_Native " , " Add C++ Class " ) : LOCTEXT ( " AddCodeWindowHeader_Blueprint " , " Add Blueprint Class " ) ;
2015-02-16 04:29:11 -05:00
}
2015-01-16 15:39:47 -05:00
2014-03-14 14:13:41 -04:00
TSharedRef < SWindow > AddCodeWindow =
SNew ( SWindow )
2015-02-16 04:29:11 -05:00
. Title ( WindowTitle )
2015-01-16 15:39:47 -05:00
. ClientSize ( WindowSize )
2014-03-14 14:13:41 -04:00
. SizingRule ( ESizingRule : : FixedSize )
. SupportsMinimize ( false ) . SupportsMaximize ( false ) ;
2015-01-26 20:16:24 -05:00
TSharedRef < SNewClassDialog > NewClassDialog =
SNew ( SNewClassDialog )
2015-02-16 04:29:11 -05:00
. Class ( Config . _ParentClass )
. ClassViewerFilter ( Config . _AllowableParents )
. ClassDomain ( InDomain )
. FeaturedClasses ( Config . _FeaturedClasses )
. InitialPath ( Config . _InitialPath )
. OnAddedToProject ( Config . _OnAddedToProject )
. DefaultClassPrefix ( Config . _DefaultClassPrefix )
. DefaultClassName ( Config . _DefaultClassName ) ;
2015-01-26 20:16:24 -05:00
AddCodeWindow - > SetContent ( NewClassDialog ) ;
2014-03-14 14:13:41 -04:00
2015-02-16 04:29:11 -05:00
TSharedPtr < SWindow > ParentWindow = Config . _ParentWindow ;
2015-01-16 15:39:47 -05:00
if ( ! ParentWindow . IsValid ( ) )
2014-03-14 14:13:41 -04:00
{
2015-01-16 15:39:47 -05:00
static const FName MainFrameModuleName = " MainFrame " ;
IMainFrameModule & MainFrameModule = FModuleManager : : LoadModuleChecked < IMainFrameModule > ( MainFrameModuleName ) ;
ParentWindow = MainFrameModule . GetParentWindow ( ) ;
}
2015-02-16 04:29:11 -05:00
if ( Config . _bModal )
2015-01-16 15:39:47 -05:00
{
2015-02-16 04:29:11 -05:00
FSlateApplication : : Get ( ) . AddModalWindow ( AddCodeWindow , ParentWindow ) ;
}
else if ( ParentWindow . IsValid ( ) )
{
FSlateApplication : : Get ( ) . AddWindowAsNativeChild ( AddCodeWindow , ParentWindow . ToSharedRef ( ) ) ;
2014-03-14 14:13:41 -04:00
}
else
{
2015-02-16 04:29:11 -05:00
FSlateApplication : : Get ( ) . AddWindow ( AddCodeWindow ) ;
2014-03-14 14:13:41 -04:00
}
}
2015-02-16 04:29:11 -05:00
bool GameProjectUtils : : IsValidClassNameForCreation ( const FString & NewClassName , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
if ( NewClassName . IsEmpty ( ) )
{
OutFailReason = LOCTEXT ( " NoClassName " , " You must specify a class name. " ) ;
return false ;
}
if ( NewClassName . Contains ( TEXT ( " " ) ) )
{
OutFailReason = LOCTEXT ( " ClassNameContainsSpace " , " Your class name may not contain a space. " ) ;
return false ;
}
if ( ! FChar : : IsAlpha ( NewClassName [ 0 ] ) )
{
OutFailReason = LOCTEXT ( " ClassNameMustBeginWithACharacter " , " Your class name must begin with an alphabetic character. " ) ;
return false ;
}
if ( NewClassName . Len ( ) > MAX_CLASS_NAME_LENGTH )
{
OutFailReason = FText : : Format ( LOCTEXT ( " ClassNameTooLong " , " The class name must not be longer than {0} characters. " ) , FText : : AsNumber ( MAX_CLASS_NAME_LENGTH ) ) ;
return false ;
}
FString IllegalNameCharacters ;
if ( ! NameContainsOnlyLegalCharacters ( NewClassName , IllegalNameCharacters ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " IllegalNameCharacters " ) , FText : : FromString ( IllegalNameCharacters ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ClassNameContainsIllegalCharacters " , " The class name may not contain the following characters: {IllegalNameCharacters} " ) , Args ) ;
return false ;
}
2015-02-16 04:29:11 -05:00
return true ;
}
bool GameProjectUtils : : IsValidClassNameForCreation ( const FString & NewClassName , const FModuleContextInfo & ModuleInfo , const TSet < FString > & DisallowedHeaderNames , FText & OutFailReason )
{
if ( ! IsValidClassNameForCreation ( NewClassName , OutFailReason ) )
{
return false ;
}
2014-03-14 14:13:41 -04:00
// Look for a duplicate class in memory
for ( TObjectIterator < UClass > ClassIt ; ClassIt ; + + ClassIt )
{
if ( ClassIt - > GetName ( ) = = NewClassName )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " NewClassName " ) , FText : : FromString ( NewClassName ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ClassNameAlreadyExists " , " The name {NewClassName} is already used by another class. " ) , Args ) ;
return false ;
}
}
// Look for a duplicate class on disk in their project
{
2014-06-18 06:45:20 -04:00
FString UnusedFoundPath ;
2014-08-04 18:21:05 -04:00
if ( FindSourceFileInProject ( NewClassName + " .h " , ModuleInfo . ModuleSourcePath , UnusedFoundPath ) )
2014-03-14 14:13:41 -04:00
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " NewClassName " ) , FText : : FromString ( NewClassName ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ClassNameAlreadyExists " , " The name {NewClassName} is already used by another class. " ) , Args ) ;
return false ;
}
}
2014-11-12 11:31:40 -05:00
// See if header name clashes with an engine header
{
FString UnusedFoundPath ;
if ( DisallowedHeaderNames . Contains ( NewClassName ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " NewHeaderName " ) , FText : : FromString ( NewClassName + " .h " ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " HeaderNameAlreadyExists " , " The file {NewHeaderName} already exists elsewhere in the engine. " ) , Args ) ;
return false ;
}
}
2014-03-14 14:13:41 -04:00
return true ;
}
2015-01-16 15:39:47 -05:00
bool GameProjectUtils : : IsValidBaseClassForCreation ( const UClass * InClass , const FModuleContextInfo & InModuleInfo )
{
auto DoesClassNeedAPIExport = [ & InModuleInfo ] ( const FString & InClassModuleName ) - > bool
{
return InModuleInfo . ModuleName ! = InClassModuleName ;
} ;
return IsValidBaseClassForCreation_Internal ( InClass , FDoesClassNeedAPIExportCallback : : CreateLambda ( DoesClassNeedAPIExport ) ) ;
}
bool GameProjectUtils : : IsValidBaseClassForCreation ( const UClass * InClass , const TArray < FModuleContextInfo > & InModuleInfoArray )
{
auto DoesClassNeedAPIExport = [ & InModuleInfoArray ] ( const FString & InClassModuleName ) - > bool
{
for ( const FModuleContextInfo & ModuleInfo : InModuleInfoArray )
{
if ( ModuleInfo . ModuleName = = InClassModuleName )
{
return false ;
}
}
return true ;
} ;
return IsValidBaseClassForCreation_Internal ( InClass , FDoesClassNeedAPIExportCallback : : CreateLambda ( DoesClassNeedAPIExport ) ) ;
}
bool GameProjectUtils : : IsValidBaseClassForCreation_Internal ( const UClass * InClass , const FDoesClassNeedAPIExportCallback & InDoesClassNeedAPIExport )
{
// You may not make native classes based on blueprint generated classes
const bool bIsBlueprintClass = ( InClass - > ClassGeneratedBy ! = nullptr ) ;
// UObject is special cased to be extensible since it would otherwise not be since it doesn't pass the API check (intrinsic class).
const bool bIsExplicitlyUObject = ( InClass = = UObject : : StaticClass ( ) ) ;
// You need API if you are not UObject itself, and you're in a module that was validated as needing API export
const FString ClassModuleName = InClass - > GetOutermost ( ) - > GetName ( ) . RightChop ( FString ( TEXT ( " /Script/ " ) ) . Len ( ) ) ;
const bool bNeedsAPI = ! bIsExplicitlyUObject & & InDoesClassNeedAPIExport . Execute ( ClassModuleName ) ;
// You may not make a class that is not DLL exported.
// MinimalAPI classes aren't compatible with the DLL export macro, but can still be used as a valid base
const bool bHasAPI = InClass - > HasAnyClassFlags ( CLASS_RequiredAPI ) | | InClass - > HasAnyClassFlags ( CLASS_MinimalAPI ) ;
// @todo should we support interfaces?
const bool bIsInterface = InClass - > IsChildOf ( UInterface : : StaticClass ( ) ) ;
return ! bIsBlueprintClass & & ( ! bNeedsAPI | | bHasAPI ) & & ! bIsInterface ;
}
2015-04-17 10:02:43 -04:00
GameProjectUtils : : EAddCodeToProjectResult GameProjectUtils : : AddCodeToProject ( const FString & NewClassName , const FString & NewClassPath , const FModuleContextInfo & ModuleInfo , const FNewClassInfo ParentClassInfo , const TSet < FString > & DisallowedHeaderNames , FString & OutHeaderFilePath , FString & OutCppFilePath , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
2015-04-17 10:02:43 -04:00
const EAddCodeToProjectResult Result = AddCodeToProject_Internal ( NewClassName , NewClassPath , ModuleInfo , ParentClassInfo , DisallowedHeaderNames , OutHeaderFilePath , OutCppFilePath , OutFailReason ) ;
2014-03-14 14:13:41 -04:00
if ( FEngineAnalytics : : IsAvailable ( ) )
{
2014-05-16 07:26:51 -04:00
const FString ParentClassName = ParentClassInfo . GetClassNameCPP ( ) ;
2014-03-14 14:13:41 -04:00
TArray < FAnalyticsEventAttribute > EventAttributes ;
2014-05-16 07:26:51 -04:00
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " ParentClass " ) , ParentClassName . IsEmpty ( ) ? TEXT ( " None " ) : ParentClassName ) ) ;
2015-04-17 10:02:43 -04:00
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " Outcome " ) , Result = = EAddCodeToProjectResult : : Succeeded ? TEXT ( " Successful " ) : TEXT ( " Failed " ) ) ) ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " FailureReason " ) , OutFailReason . ToString ( ) ) ) ;
2014-03-14 14:13:41 -04:00
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Editor.AddCodeToProject.CodeAdded " ) , EventAttributes ) ;
}
2015-04-17 10:02:43 -04:00
return Result ;
2014-03-14 14:13:41 -04:00
}
UTemplateProjectDefs * GameProjectUtils : : LoadTemplateDefs ( const FString & ProjectDirectory )
{
UTemplateProjectDefs * TemplateDefs = NULL ;
const FString TemplateDefsIniFilename = ProjectDirectory / TEXT ( " Config " ) / GetTemplateDefsFilename ( ) ;
if ( FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . FileExists ( * TemplateDefsIniFilename ) )
{
2014-08-11 17:14:26 -04:00
UClass * ClassToConstruct = UDefaultTemplateProjectDefs : : StaticClass ( ) ;
// see if template uses a custom project defs object
FString ClassName ;
const bool bFoundValue = GConfig - > GetString ( * UTemplateProjectDefs : : StaticClass ( ) - > GetPathName ( ) , TEXT ( " TemplateProjectDefsClass " ) , ClassName , TemplateDefsIniFilename ) ;
if ( bFoundValue & & ClassName . Len ( ) > 0 )
{
UClass * OverrideClass = FindObject < UClass > ( ANY_PACKAGE , * ClassName , false ) ;
if ( nullptr ! = OverrideClass )
{
ClassToConstruct = OverrideClass ;
}
else
{
UE_LOG ( LogGameProjectGeneration , Error , TEXT ( " Failed to find template project defs class '%s', using default. " ) , * ClassName ) ;
}
}
2015-02-03 05:40:57 -05:00
TemplateDefs = NewObject < UTemplateProjectDefs > ( GetTransientPackage ( ) , ClassToConstruct ) ;
2014-03-14 14:13:41 -04:00
TemplateDefs - > LoadConfig ( UTemplateProjectDefs : : StaticClass ( ) , * TemplateDefsIniFilename ) ;
}
return TemplateDefs ;
}
2015-04-09 11:11:48 -04:00
bool GameProjectUtils : : GenerateProjectFromScratch ( const FProjectInformation & InProjectInfo , FText & OutFailReason , FText & OutFailLog )
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
FScopedSlowTask SlowTask ( 5 ) ;
2014-09-09 12:16:36 -04:00
const FString NewProjectFolder = FPaths : : GetPath ( InProjectInfo . ProjectFilename ) ;
const FString NewProjectName = FPaths : : GetBaseFilename ( InProjectInfo . ProjectFilename ) ;
2014-03-14 14:13:41 -04:00
TArray < FString > CreatedFiles ;
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Generate config files
2014-09-09 12:16:36 -04:00
if ( ! GenerateConfigFiles ( InProjectInfo , CreatedFiles , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
2015-02-09 10:40:24 -05:00
// Insert any required feature packs (EG starter content) into ini file. These will be imported automatically when the editor is first run
2015-04-27 15:47:43 -04:00
if ( ! InsertFeaturePacksIntoINIFile ( InProjectInfo , OutFailReason ) )
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
2015-02-09 10:40:24 -05:00
2014-03-14 14:13:41 -04:00
// Make the Content folder
const FString ContentFolder = NewProjectFolder / TEXT ( " Content " ) ;
if ( ! IFileManager : : Get ( ) . MakeDirectory ( * ContentFolder ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ContentFolder " ) , FText : : FromString ( ContentFolder ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToCreateContentFolder " , " Failed to create the content folder {ContentFolder} " ) , Args ) ;
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
TArray < FString > StartupModuleNames ;
2014-09-09 12:16:36 -04:00
if ( InProjectInfo . bShouldGenerateCode )
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
FScopedSlowTask LocalScope ( 2 ) ;
LocalScope . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Generate basic source code files
2014-09-23 13:55:06 -04:00
if ( ! GenerateBasicSourceCode ( NewProjectFolder / TEXT ( " Source " ) , NewProjectName , NewProjectFolder , StartupModuleNames , CreatedFiles , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
2014-10-08 04:42:34 -04:00
LocalScope . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Generate game framework source code files
if ( ! GenerateGameFrameworkSourceCode ( NewProjectFolder / TEXT ( " Source " ) , NewProjectName , CreatedFiles , OutFailReason ) )
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Generate the project file
{
2015-01-09 08:08:13 -05:00
// Set up the descriptor
FProjectDescriptor Descriptor ;
for ( int32 Idx = 0 ; Idx < StartupModuleNames . Num ( ) ; Idx + + )
2014-03-14 14:13:41 -04:00
{
2015-01-09 08:08:13 -05:00
Descriptor . Modules . Add ( FModuleDescriptor ( * StartupModuleNames [ Idx ] ) ) ;
2014-03-14 14:13:41 -04:00
}
2015-01-09 08:08:13 -05:00
// Try to save it
FText LocalFailReason ;
if ( ! Descriptor . Save ( InProjectInfo . ProjectFilename , LocalFailReason ) )
2014-03-14 14:13:41 -04:00
{
OutFailReason = LocalFailReason ;
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
2015-01-09 08:08:13 -05:00
CreatedFiles . Add ( InProjectInfo . ProjectFilename ) ;
2014-11-10 17:04:43 -05:00
// Set the engine identifier for it. Do this after saving, so it can be correctly detected as foreign or non-foreign.
if ( ! SetEngineAssociationForForeignProject ( InProjectInfo . ProjectFilename , OutFailReason ) )
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
2014-03-14 14:13:41 -04:00
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-09-09 12:16:36 -04:00
if ( InProjectInfo . bShouldGenerateCode )
2014-03-14 14:13:41 -04:00
{
// Generate project files
2015-04-09 11:11:48 -04:00
if ( ! GenerateCodeProjectFiles ( InProjectInfo . ProjectFilename , OutFailReason , OutFailLog ) )
2014-03-14 14:13:41 -04:00
{
2014-09-09 12:16:36 -04:00
DeleteGeneratedProjectFiles ( InProjectInfo . ProjectFilename ) ;
2014-03-14 14:13:41 -04:00
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
UE_LOG ( LogGameProjectGeneration , Log , TEXT ( " Created new project with %d files (plus project files) " ) , CreatedFiles . Num ( ) ) ;
return true ;
}
2015-04-09 11:11:48 -04:00
bool GameProjectUtils : : CreateProjectFromTemplate ( const FProjectInformation & InProjectInfo , FText & OutFailReason , FText & OutFailLog )
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
FScopedSlowTask SlowTask ( 10 ) ;
2014-09-09 12:16:36 -04:00
const FString ProjectName = FPaths : : GetBaseFilename ( InProjectInfo . ProjectFilename ) ;
const FString TemplateName = FPaths : : GetBaseFilename ( InProjectInfo . TemplateFile ) ;
const FString SrcFolder = FPaths : : GetPath ( InProjectInfo . TemplateFile ) ;
const FString DestFolder = FPaths : : GetPath ( InProjectInfo . ProjectFilename ) ;
2014-03-14 14:13:41 -04:00
2014-09-09 12:16:36 -04:00
if ( ! FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . FileExists ( * InProjectInfo . TemplateFile ) )
2014-03-14 14:13:41 -04:00
{
FFormatNamedArguments Args ;
2014-09-09 12:16:36 -04:00
Args . Add ( TEXT ( " TemplateFile " ) , FText : : FromString ( InProjectInfo . TemplateFile ) ) ;
2014-03-14 14:13:41 -04:00
OutFailReason = FText : : Format ( LOCTEXT ( " InvalidTemplate_MissingProject " , " Template project \" {TemplateFile} \" does not exist. " ) , Args ) ;
return false ;
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
UTemplateProjectDefs * TemplateDefs = LoadTemplateDefs ( SrcFolder ) ;
if ( TemplateDefs = = NULL )
{
FFormatNamedArguments Args ;
2014-09-09 12:16:36 -04:00
Args . Add ( TEXT ( " TemplateFile " ) , FText : : FromString ( FPaths : : GetBaseFilename ( InProjectInfo . TemplateFile ) ) ) ;
2014-03-14 14:13:41 -04:00
Args . Add ( TEXT ( " TemplateDefinesFile " ) , FText : : FromString ( GetTemplateDefsFilename ( ) ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " InvalidTemplate_MissingDefs " , " Template project \" {TemplateFile} \" does not have definitions file: '{TemplateDefinesFile}'. " ) , Args ) ;
return false ;
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Fix up the replacement strings using the specified project name
TemplateDefs - > FixupStrings ( TemplateName , ProjectName ) ;
// Form a list of all extensions we care about
TSet < FString > ReplacementsInFilesExtensions ;
for ( auto ReplacementIt = TemplateDefs - > ReplacementsInFiles . CreateConstIterator ( ) ; ReplacementIt ; + + ReplacementIt )
{
ReplacementsInFilesExtensions . Append ( ( * ReplacementIt ) . Extensions ) ;
}
// Keep a list of created files so we can delete them if project creation fails
TArray < FString > CreatedFiles ;
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Discover and copy all files in the src folder to the destination, excluding a few files and folders
TArray < FString > FilesToCopy ;
TArray < FString > FilesThatNeedContentsReplaced ;
TMap < FString , FString > ClassRenames ;
IFileManager : : Get ( ) . FindFilesRecursive ( FilesToCopy , * SrcFolder , TEXT ( " * " ) , /*Files=*/ true , /*Directories=*/ false ) ;
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
// Open a new feedback scope for the loop so we can report how far through the copy we are
FScopedSlowTask InnerSlowTask ( FilesToCopy . Num ( ) ) ;
for ( auto FileIt = FilesToCopy . CreateConstIterator ( ) ; FileIt ; + + FileIt )
{
const FString SrcFilename = ( * FileIt ) ;
// Update the progress
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " SrcFilename " ) , FText : : FromString ( FPaths : : GetCleanFilename ( SrcFilename ) ) ) ;
InnerSlowTask . EnterProgressFrame ( 1 , FText : : Format ( LOCTEXT ( " CreatingProjectStatus_CopyingFile " , " Copying File {SrcFilename}... " ) , Args ) ) ;
2014-03-14 14:13:41 -04:00
2014-12-12 15:38:16 -05:00
// Get the file path, relative to the src folder
2014-10-08 04:42:34 -04:00
const FString SrcFileSubpath = SrcFilename . RightChop ( SrcFolder . Len ( ) + 1 ) ;
2014-03-14 14:13:41 -04:00
2014-10-08 04:42:34 -04:00
// Skip any files that were configured to be ignored
bool bThisFileIsIgnored = false ;
for ( auto IgnoreIt = TemplateDefs - > FilesToIgnore . CreateConstIterator ( ) ; IgnoreIt ; + + IgnoreIt )
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
if ( SrcFileSubpath = = * IgnoreIt )
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
// This file was marked as "ignored"
bThisFileIsIgnored = true ;
break ;
2014-03-14 14:13:41 -04:00
}
}
2014-10-08 04:42:34 -04:00
if ( bThisFileIsIgnored )
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
// This file was marked as "ignored"
continue ;
}
// Skip any folders that were configured to be ignored
bool bThisFolderIsIgnored = false ;
for ( auto IgnoreIt = TemplateDefs - > FoldersToIgnore . CreateConstIterator ( ) ; IgnoreIt ; + + IgnoreIt )
{
if ( SrcFileSubpath . StartsWith ( ( * IgnoreIt ) + TEXT ( " / " ) ) )
{
// This folder was marked as "ignored"
2014-12-12 15:38:16 -05:00
UE_LOG ( LogGameProjectGeneration , Verbose , TEXT ( " '%s': Skipping as it is in an ignored folder '%s' " ) , * SrcFilename , * * IgnoreIt ) ;
2014-10-08 04:42:34 -04:00
bThisFolderIsIgnored = true ;
break ;
}
}
if ( bThisFolderIsIgnored )
{
// This folder was marked as "ignored"
continue ;
}
// Retarget any folders that were chosen to be renamed by choosing a new destination subpath now
FString DestFileSubpathWithoutFilename = FPaths : : GetPath ( SrcFileSubpath ) + TEXT ( " / " ) ;
for ( auto RenameIt = TemplateDefs - > FolderRenames . CreateConstIterator ( ) ; RenameIt ; + + RenameIt )
{
const FTemplateFolderRename & FolderRename = * RenameIt ;
if ( SrcFileSubpath . StartsWith ( FolderRename . From + TEXT ( " / " ) ) )
{
// This was a file in a renamed folder. Retarget to the new location
DestFileSubpathWithoutFilename = FolderRename . To / DestFileSubpathWithoutFilename . RightChop ( FolderRename . From . Len ( ) ) ;
2014-12-12 15:38:16 -05:00
UE_LOG ( LogGameProjectGeneration , Verbose , TEXT ( " '%s': Moving to '%s' as it matched folder rename ('%s'->'%s') " ) , * SrcFilename , * DestFileSubpathWithoutFilename , * FolderRename . From , * FolderRename . To ) ;
2014-10-08 04:42:34 -04:00
}
}
// Retarget any files that were chosen to have parts of their names replaced here
FString DestBaseFilename = FPaths : : GetBaseFilename ( SrcFileSubpath ) ;
const FString FileExtension = FPaths : : GetExtension ( SrcFileSubpath ) ;
for ( auto ReplacementIt = TemplateDefs - > FilenameReplacements . CreateConstIterator ( ) ; ReplacementIt ; + + ReplacementIt )
{
const FTemplateReplacement & Replacement = * ReplacementIt ;
if ( Replacement . Extensions . Contains ( FileExtension ) )
{
// This file matched a filename replacement extension, apply it now
2014-12-12 15:38:16 -05:00
FString LastDestBaseFilename = DestBaseFilename ;
2014-10-08 04:42:34 -04:00
DestBaseFilename = DestBaseFilename . Replace ( * Replacement . From , * Replacement . To , Replacement . bCaseSensitive ? ESearchCase : : CaseSensitive : ESearchCase : : IgnoreCase ) ;
2014-12-12 15:38:16 -05:00
if ( LastDestBaseFilename ! = DestBaseFilename )
{
UE_LOG ( LogGameProjectGeneration , Verbose , TEXT ( " '%s': Renaming to '%s/%s' as it matched file rename ('%s'->'%s') " ) , * SrcFilename , * DestFileSubpathWithoutFilename , * DestBaseFilename , * Replacement . From , * Replacement . To ) ;
}
2014-10-08 04:42:34 -04:00
}
}
// Perform the copy
const FString DestFilename = DestFolder / DestFileSubpathWithoutFilename + DestBaseFilename + TEXT ( " . " ) + FileExtension ;
if ( IFileManager : : Get ( ) . Copy ( * DestFilename , * SrcFilename ) = = COPY_OK )
{
CreatedFiles . Add ( DestFilename ) ;
if ( ReplacementsInFilesExtensions . Contains ( FileExtension ) )
{
FilesThatNeedContentsReplaced . Add ( DestFilename ) ;
}
// Allow project template to extract class renames from this file copy
if ( FPaths : : GetBaseFilename ( SrcFilename ) ! = FPaths : : GetBaseFilename ( DestFilename )
& & TemplateDefs - > IsClassRename ( DestFilename , SrcFilename , FileExtension ) )
{
// Looks like a UObject file!
ClassRenames . Add ( FPaths : : GetBaseFilename ( SrcFilename ) , FPaths : : GetBaseFilename ( DestFilename ) ) ;
}
}
else
{
FFormatNamedArguments FailArgs ;
FailArgs . Add ( TEXT ( " SrcFilename " ) , FText : : FromString ( SrcFilename ) ) ;
FailArgs . Add ( TEXT ( " DestFilename " ) , FText : : FromString ( DestFilename ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToCopyFile " , " Failed to copy \" {SrcFilename} \" to \" {DestFilename} \" . " ) , FailArgs ) ;
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
2014-03-14 14:13:41 -04:00
}
}
2014-10-08 04:42:34 -04:00
}
2014-03-14 14:13:41 -04:00
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
{
// Open a new feedback scope for the loop so we can report how far through the process we are
FScopedSlowTask InnerSlowTask ( FilesThatNeedContentsReplaced . Num ( ) ) ;
// Open all files with the specified extensions and replace text
for ( auto FileIt = FilesThatNeedContentsReplaced . CreateConstIterator ( ) ; FileIt ; + + FileIt )
2014-03-14 14:13:41 -04:00
{
2014-10-08 04:42:34 -04:00
InnerSlowTask . EnterProgressFrame ( ) ;
const FString FileToFix = * FileIt ;
bool bSuccessfullyProcessed = false ;
FString FileContents ;
if ( FFileHelper : : LoadFileToString ( FileContents , * FileToFix ) )
{
for ( auto ReplacementIt = TemplateDefs - > ReplacementsInFiles . CreateConstIterator ( ) ; ReplacementIt ; + + ReplacementIt )
{
const FTemplateReplacement & Replacement = * ReplacementIt ;
if ( Replacement . Extensions . Contains ( FPaths : : GetExtension ( FileToFix ) ) )
{
FileContents = FileContents . Replace ( * Replacement . From , * Replacement . To , Replacement . bCaseSensitive ? ESearchCase : : CaseSensitive : ESearchCase : : IgnoreCase ) ;
}
}
if ( FFileHelper : : SaveStringToFile ( FileContents , * FileToFix ) )
{
bSuccessfullyProcessed = true ;
}
}
if ( ! bSuccessfullyProcessed )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " FileToFix " ) , FText : : FromString ( FileToFix ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToFixUpFile " , " Failed to process file \" {FileToFix} \" . " ) , Args ) ;
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
2014-03-14 14:13:41 -04:00
}
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-09-09 12:16:36 -04:00
const FString ProjectConfigPath = DestFolder / TEXT ( " Config " ) ;
// Write out the hardware class target settings chosen for this project
{
2014-10-10 18:18:31 -04:00
const FString DefaultEngineIniFilename = ProjectConfigPath / TEXT ( " DefaultEngine.ini " ) ;
2014-09-09 12:16:36 -04:00
FString FileContents ;
// Load the existing file - if it doesn't exist we create it
2014-10-10 18:18:31 -04:00
FFileHelper : : LoadFileToString ( FileContents , * DefaultEngineIniFilename ) ;
2014-09-09 12:16:36 -04:00
FileContents + = LINE_TERMINATOR ;
FileContents + = GetHardwareConfigString ( InProjectInfo ) ;
2014-10-10 18:18:31 -04:00
if ( ! WriteOutputFile ( DefaultEngineIniFilename , FileContents , OutFailReason ) )
2014-09-09 12:16:36 -04:00
{
return false ;
}
}
2014-03-14 14:13:41 -04:00
// Fixup specific ini values
2014-08-11 17:14:26 -04:00
TArray < FTemplateConfigValue > ConfigValuesToSet ;
2014-09-09 12:16:36 -04:00
TemplateDefs - > AddConfigValues ( ConfigValuesToSet , TemplateName , ProjectName , InProjectInfo . bShouldGenerateCode ) ;
2014-08-11 17:14:26 -04:00
new ( ConfigValuesToSet ) FTemplateConfigValue ( TEXT ( " DefaultGame.ini " ) , TEXT ( " /Script/EngineSettings.GeneralProjectSettings " ) , TEXT ( " ProjectID " ) , FGuid : : NewGuid ( ) . ToString ( ) , /*InShouldReplaceExistingValue=*/ true ) ;
2014-03-14 14:13:41 -04:00
// Add all classname fixups
for ( auto RenameIt = ClassRenames . CreateConstIterator ( ) ; RenameIt ; + + RenameIt )
{
const FString ClassRedirectString = FString : : Printf ( TEXT ( " (OldClassName= \" %s \" ,NewClassName= \" %s \" ) " ) , * RenameIt . Key ( ) , * RenameIt . Value ( ) ) ;
2014-08-11 17:14:26 -04:00
new ( ConfigValuesToSet ) FTemplateConfigValue ( TEXT ( " DefaultEngine.ini " ) , TEXT ( " /Script/Engine.Engine " ) , TEXT ( " +ActiveClassRedirects " ) , * ClassRedirectString , /*InShouldReplaceExistingValue=*/ false ) ;
2014-03-14 14:13:41 -04:00
}
// Fix all specified config values
for ( auto ConfigIt = ConfigValuesToSet . CreateConstIterator ( ) ; ConfigIt ; + + ConfigIt )
{
2014-08-11 17:14:26 -04:00
const FTemplateConfigValue & ConfigValue = * ConfigIt ;
2014-09-09 12:16:36 -04:00
const FString IniFilename = ProjectConfigPath / ConfigValue . ConfigFile ;
2014-03-14 14:13:41 -04:00
bool bSuccessfullyProcessed = false ;
TArray < FString > FileLines ;
if ( FFileHelper : : LoadANSITextFileToStrings ( * IniFilename , & IFileManager : : Get ( ) , FileLines ) )
{
FString FileOutput ;
const FString TargetSection = ConfigValue . ConfigSection ;
FString CurSection ;
bool bFoundTargetKey = false ;
for ( auto LineIt = FileLines . CreateConstIterator ( ) ; LineIt ; + + LineIt )
{
FString Line = * LineIt ;
Line . Trim ( ) . TrimTrailing ( ) ;
bool bShouldExcludeLineFromOutput = false ;
// If we not yet found the target key parse each line looking for it
if ( ! bFoundTargetKey )
{
// Check for an empty line. No work needs to be done on these lines
if ( Line . Len ( ) = = 0 )
{
}
// Comment lines start with ";". Skip these lines entirely.
else if ( Line . StartsWith ( TEXT ( " ; " ) ) )
{
}
// If this is a section line, update the section
else if ( Line . StartsWith ( TEXT ( " [ " ) ) )
{
// If we are entering a new section and we have not yet found our key in the target section, add it to the end of the section
if ( CurSection = = TargetSection )
{
FileOutput + = ConfigValue . ConfigKey + TEXT ( " = " ) + ConfigValue . ConfigValue + LINE_TERMINATOR + LINE_TERMINATOR ;
bFoundTargetKey = true ;
}
// Update the current section
CurSection = Line . Mid ( 1 , Line . Len ( ) - 2 ) ;
}
// This is possibly an actual key/value pair
else if ( CurSection = = TargetSection )
{
// Key value pairs contain an equals sign
const int32 EqualsIdx = Line . Find ( TEXT ( " = " ) ) ;
if ( EqualsIdx ! = INDEX_NONE )
{
// Determine the key and see if it is the target key
const FString Key = Line . Left ( EqualsIdx ) ;
if ( Key = = ConfigValue . ConfigKey )
{
// Found the target key, add it to the output and skip the current line if the target value is supposed to replace
FileOutput + = ConfigValue . ConfigKey + TEXT ( " = " ) + ConfigValue . ConfigValue + LINE_TERMINATOR ;
bShouldExcludeLineFromOutput = ConfigValue . bShouldReplaceExistingValue ;
bFoundTargetKey = true ;
}
}
}
}
// Unless we replaced the key, add this line to the output
if ( ! bShouldExcludeLineFromOutput )
{
FileOutput + = Line ;
if ( LineIt . GetIndex ( ) < FileLines . Num ( ) - 1 )
{
// Add a line terminator on every line except the last
FileOutput + = LINE_TERMINATOR ;
}
}
}
// If the key did not exist, add it here
if ( ! bFoundTargetKey )
{
// If we did not end in the correct section, add the section to the bottom of the file
if ( CurSection ! = TargetSection )
{
FileOutput + = LINE_TERMINATOR ;
FileOutput + = LINE_TERMINATOR ;
FileOutput + = FString : : Printf ( TEXT ( " [%s] " ) , * TargetSection ) + LINE_TERMINATOR ;
}
// Add the key/value here
FileOutput + = ConfigValue . ConfigKey + TEXT ( " = " ) + ConfigValue . ConfigValue + LINE_TERMINATOR ;
}
if ( FFileHelper : : SaveStringToFile ( FileOutput , * IniFilename ) )
{
bSuccessfullyProcessed = true ;
}
}
if ( ! bSuccessfullyProcessed )
{
OutFailReason = LOCTEXT ( " FailedToFixUpDefaultEngine " , " Failed to process file DefaultEngine.ini " ) ;
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
}
2015-02-09 10:40:24 -05:00
// Insert any required feature packs (EG starter content) into ini file. These will be imported automatically when the editor is first run
2015-04-27 15:47:43 -04:00
if ( ! InsertFeaturePacksIntoINIFile ( InProjectInfo , OutFailReason ) )
{
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
2015-02-09 10:40:24 -05:00
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Generate the project file
{
2014-07-22 15:58:02 -04:00
// Load the source project
FProjectDescriptor Project ;
2014-09-09 12:16:36 -04:00
if ( ! Project . Load ( InProjectInfo . TemplateFile , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
2014-07-22 15:58:02 -04:00
// Update it to current
2014-11-10 17:04:43 -05:00
Project . EngineAssociation . Empty ( ) ;
2014-07-22 15:58:02 -04:00
Project . EpicSampleNameHash = 0 ;
// Fix up module names
2014-09-09 12:16:36 -04:00
const FString BaseSourceName = FPaths : : GetBaseFilename ( InProjectInfo . TemplateFile ) ;
const FString BaseNewName = FPaths : : GetBaseFilename ( InProjectInfo . ProjectFilename ) ;
2014-07-22 15:58:02 -04:00
for ( auto ModuleIt = Project . Modules . CreateIterator ( ) ; ModuleIt ; + + ModuleIt )
{
FModuleDescriptor & ModuleInfo = * ModuleIt ;
ModuleInfo . Name = FName ( * ModuleInfo . Name . ToString ( ) . Replace ( * BaseSourceName , * BaseNewName ) ) ;
}
// Save it to disk
2014-09-09 12:16:36 -04:00
if ( ! Project . Save ( InProjectInfo . ProjectFilename , OutFailReason ) )
2014-07-22 15:58:02 -04:00
{
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
2014-11-10 17:04:43 -05:00
// Set the engine identifier if it's a foreign project. Do this after saving, so it can be correctly detected as foreign.
if ( ! SetEngineAssociationForForeignProject ( InProjectInfo . ProjectFilename , OutFailReason ) )
{
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
2014-07-22 15:58:02 -04:00
// Add it to the list of created files
2014-09-09 12:16:36 -04:00
CreatedFiles . Add ( InProjectInfo . ProjectFilename ) ;
2014-03-14 14:13:41 -04:00
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
SlowTask . EnterProgressFrame ( ) ;
2014-09-09 12:16:36 -04:00
if ( InProjectInfo . bShouldGenerateCode )
2014-03-14 14:13:41 -04:00
{
// Generate project files
2015-04-09 11:11:48 -04:00
if ( ! GenerateCodeProjectFiles ( InProjectInfo . ProjectFilename , OutFailReason , OutFailLog ) )
2014-03-14 14:13:41 -04:00
{
2014-09-09 12:16:36 -04:00
DeleteGeneratedProjectFiles ( InProjectInfo . ProjectFilename ) ;
2014-03-14 14:13:41 -04:00
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-09-09 12:16:36 -04:00
if ( ! TemplateDefs - > PostGenerateProject ( DestFolder , SrcFolder , InProjectInfo . ProjectFilename , InProjectInfo . TemplateFile , InProjectInfo . bShouldGenerateCode , OutFailReason ) )
2014-08-11 17:14:26 -04:00
{
2014-09-09 12:16:36 -04:00
DeleteGeneratedProjectFiles ( InProjectInfo . ProjectFilename ) ;
2014-08-11 17:14:26 -04:00
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
2014-03-14 14:13:41 -04:00
return true ;
}
2014-11-10 17:04:43 -05:00
bool GameProjectUtils : : SetEngineAssociationForForeignProject ( const FString & ProjectFileName , FText & OutFailReason )
{
if ( FUProjectDictionary ( FPaths : : RootDir ( ) ) . IsForeignProject ( ProjectFileName ) )
{
if ( ! FDesktopPlatformModule : : Get ( ) - > SetEngineIdentifierForProject ( ProjectFileName , FDesktopPlatformModule : : Get ( ) - > GetCurrentEngineIdentifier ( ) ) )
{
OutFailReason = LOCTEXT ( " FailedToSetEngineIdentifier " , " Couldn't set engine identifier for project " ) ;
return false ;
}
}
return true ;
}
2014-03-14 14:13:41 -04:00
FString GameProjectUtils : : GetTemplateDefsFilename ( )
{
return TEXT ( " TemplateDefs.ini " ) ;
}
bool GameProjectUtils : : NameContainsOnlyLegalCharacters ( const FString & TestName , FString & OutIllegalCharacters )
{
bool bContainsIllegalCharacters = false ;
// Only allow alphanumeric characters in the project name
bool bFoundAlphaNumericChar = false ;
for ( int32 CharIdx = 0 ; CharIdx < TestName . Len ( ) ; + + CharIdx )
{
const FString & Char = TestName . Mid ( CharIdx , 1 ) ;
if ( ! FChar : : IsAlnum ( Char [ 0 ] ) & & Char ! = TEXT ( " _ " ) )
{
if ( ! OutIllegalCharacters . Contains ( Char ) )
{
OutIllegalCharacters + = Char ;
}
bContainsIllegalCharacters = true ;
}
}
return ! bContainsIllegalCharacters ;
}
2014-05-08 15:03:34 -04:00
bool GameProjectUtils : : NameContainsUnderscoreAndXB1Installed ( const FString & TestName )
{
2015-03-04 08:31:40 -05:00
// disabled for now so people with the SDK installed can use the editor
return false ;
2014-05-08 15:03:34 -04:00
bool bContainsIllegalCharacters = false ;
// Only allow alphanumeric characters in the project name
for ( int32 CharIdx = 0 ; CharIdx < TestName . Len ( ) ; + + CharIdx )
{
const FString & Char = TestName . Mid ( CharIdx , 1 ) ;
if ( Char = = TEXT ( " _ " ) )
{
const ITargetPlatform * Platform = GetTargetPlatformManager ( ) - > FindTargetPlatform ( TEXT ( " XboxOne " ) ) ;
if ( Platform )
{
FString NotInstalledDocLink ;
if ( Platform - > IsSdkInstalled ( true , NotInstalledDocLink ) )
{
bContainsIllegalCharacters = true ;
}
}
}
}
return bContainsIllegalCharacters ;
}
2014-03-14 14:13:41 -04:00
bool GameProjectUtils : : ProjectFileExists ( const FString & ProjectFile )
{
return FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . FileExists ( * ProjectFile ) ;
}
bool GameProjectUtils : : AnyProjectFilesExistInFolder ( const FString & Path )
{
TArray < FString > ExistingFiles ;
2014-09-08 13:51:36 -04:00
const FString Wildcard = FString : : Printf ( TEXT ( " %s/*.%s " ) , * Path , * FProjectDescriptor : : GetExtension ( ) ) ;
2014-03-14 14:13:41 -04:00
IFileManager : : Get ( ) . FindFiles ( ExistingFiles , * Wildcard , /*Files=*/ true , /*Directories=*/ false ) ;
return ExistingFiles . Num ( ) > 0 ;
}
bool GameProjectUtils : : CleanupIsEnabled ( )
{
// Clean up files when running Rocket (unless otherwise specified on the command line)
return FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " norocketcleanup " ) ) = = false ;
}
void GameProjectUtils : : DeleteCreatedFiles ( const FString & RootFolder , const TArray < FString > & CreatedFiles )
{
if ( CleanupIsEnabled ( ) )
{
for ( auto FileToDeleteIt = CreatedFiles . CreateConstIterator ( ) ; FileToDeleteIt ; + + FileToDeleteIt )
{
IFileManager : : Get ( ) . Delete ( * * FileToDeleteIt ) ;
}
// If the project folder is empty after deleting all the files we created, delete the directory as well
TArray < FString > RemainingFiles ;
IFileManager : : Get ( ) . FindFilesRecursive ( RemainingFiles , * RootFolder , TEXT ( " *.* " ) , /*Files=*/ true , /*Directories=*/ false ) ;
if ( RemainingFiles . Num ( ) = = 0 )
{
IFileManager : : Get ( ) . DeleteDirectory ( * RootFolder , /*RequireExists=*/ false , /*Tree=*/ true ) ;
}
}
}
void GameProjectUtils : : DeleteGeneratedProjectFiles ( const FString & NewProjectFile )
{
if ( CleanupIsEnabled ( ) )
{
const FString NewProjectFolder = FPaths : : GetPath ( NewProjectFile ) ;
const FString NewProjectName = FPaths : : GetBaseFilename ( NewProjectFile ) ;
// Since it is hard to tell which files were created from the code project file generation process, just delete the entire ProjectFiles folder.
const FString IntermediateProjectFileFolder = NewProjectFolder / TEXT ( " Intermediate " ) / TEXT ( " ProjectFiles " ) ;
IFileManager : : Get ( ) . DeleteDirectory ( * IntermediateProjectFileFolder , /*RequireExists=*/ false , /*Tree=*/ true ) ;
// Delete the solution file
const FString SolutionFileName = NewProjectFolder / NewProjectName + TEXT ( " .sln " ) ;
IFileManager : : Get ( ) . Delete ( * SolutionFileName ) ;
}
}
void GameProjectUtils : : DeleteGeneratedBuildFiles ( const FString & NewProjectFolder )
{
if ( CleanupIsEnabled ( ) )
{
// Since it is hard to tell which files were created from the build process, just delete the entire Binaries and Build folders.
const FString BinariesFolder = NewProjectFolder / TEXT ( " Binaries " ) ;
const FString BuildFolder = NewProjectFolder / TEXT ( " Intermediate " ) / TEXT ( " Build " ) ;
IFileManager : : Get ( ) . DeleteDirectory ( * BinariesFolder , /*RequireExists=*/ false , /*Tree=*/ true ) ;
IFileManager : : Get ( ) . DeleteDirectory ( * BuildFolder , /*RequireExists=*/ false , /*Tree=*/ true ) ;
}
}
2014-09-09 12:16:36 -04:00
FString GameProjectUtils : : GetHardwareConfigString ( const FProjectInformation & InProjectInfo )
2014-03-14 14:13:41 -04:00
{
2014-09-09 12:16:36 -04:00
FString HardwareTargeting ;
2014-10-10 18:18:31 -04:00
FString TargetHardwareAsString ;
2015-01-20 09:33:54 -05:00
UEnum : : GetValueAsString ( TEXT ( " /Script/HardwareTargeting.EHardwareClass " ) , InProjectInfo . TargetedHardware , /*out*/ TargetHardwareAsString ) ;
2014-10-10 18:18:31 -04:00
FString GraphicsPresetAsString ;
2015-01-20 09:33:54 -05:00
UEnum : : GetValueAsString ( TEXT ( " /Script/HardwareTargeting.EGraphicsPreset " ) , InProjectInfo . DefaultGraphicsPerformance , /*out*/ GraphicsPresetAsString ) ;
2014-10-10 18:18:31 -04:00
2014-09-09 12:16:36 -04:00
HardwareTargeting + = TEXT ( " [/Script/HardwareTargeting.HardwareTargetingSettings] " ) LINE_TERMINATOR ;
2014-10-10 18:18:31 -04:00
HardwareTargeting + = FString : : Printf ( TEXT ( " TargetedHardwareClass=%s " ) LINE_TERMINATOR , * TargetHardwareAsString ) ;
HardwareTargeting + = FString : : Printf ( TEXT ( " DefaultGraphicsPerformance=%s " ) LINE_TERMINATOR , * GraphicsPresetAsString ) ;
2014-09-09 12:16:36 -04:00
HardwareTargeting + = LINE_TERMINATOR ;
return HardwareTargeting ;
}
bool GameProjectUtils : : GenerateConfigFiles ( const FProjectInformation & InProjectInfo , TArray < FString > & OutCreatedFiles , FText & OutFailReason )
{
const FString NewProjectFolder = FPaths : : GetPath ( InProjectInfo . ProjectFilename ) ;
const FString NewProjectName = FPaths : : GetBaseFilename ( InProjectInfo . ProjectFilename ) ;
FString ProjectConfigPath = NewProjectFolder / TEXT ( " Config " ) ;
2014-03-14 14:13:41 -04:00
// DefaultEngine.ini
{
const FString DefaultEngineIniFilename = ProjectConfigPath / TEXT ( " DefaultEngine.ini " ) ;
FString FileContents ;
FileContents + = TEXT ( " [URL] " ) LINE_TERMINATOR ;
2014-10-10 18:18:31 -04:00
FileContents + = GetHardwareConfigString ( InProjectInfo ) ;
FileContents + = LINE_TERMINATOR ;
2014-09-09 12:16:36 -04:00
if ( InProjectInfo . bCopyStarterContent )
2014-03-14 14:13:41 -04:00
{
2014-12-01 19:04:12 -05:00
FString SpecificEditorStartupMap ;
2015-02-09 10:40:24 -05:00
FString SpecificGameDefaultMap ;
2014-12-01 19:04:12 -05:00
2015-02-09 10:40:24 -05:00
// If we have starter content packs available, specify starter map
if ( IsStarterContentAvailableForNewProjects ( ) = = true )
2014-10-01 10:58:58 -04:00
{
2015-02-09 10:40:24 -05:00
if ( InProjectInfo . TargetedHardware = = EHardwareClass : : Mobile )
2014-10-01 10:58:58 -04:00
{
2015-03-06 11:59:18 -05:00
SpecificEditorStartupMap = TEXT ( " /Game/MobileStarterContent/Maps/Minimal_Default " ) ;
SpecificGameDefaultMap = TEXT ( " /Game/MobileStarterContent/Maps/Minimal_Default " ) ;
2014-10-01 10:58:58 -04:00
}
2015-02-09 10:40:24 -05:00
else
{
2015-03-06 11:59:18 -05:00
SpecificEditorStartupMap = TEXT ( " /Game/StarterContent/Maps/Minimal_Default " ) ;
SpecificGameDefaultMap = TEXT ( " /Game/StarterContent/Maps/Minimal_Default " ) ;
2015-02-09 10:40:24 -05:00
}
2015-03-06 11:59:18 -05:00
}
2014-10-01 10:58:58 -04:00
// Write out the settings for startup map and game default map
FileContents + = TEXT ( " [/Script/EngineSettings.GameMapsSettings] " ) LINE_TERMINATOR ;
FileContents + = FString : : Printf ( TEXT ( " EditorStartupMap=%s " ) LINE_TERMINATOR , * SpecificEditorStartupMap ) ;
FileContents + = FString : : Printf ( TEXT ( " GameDefaultMap=%s " ) LINE_TERMINATOR , * SpecificGameDefaultMap ) ;
if ( InProjectInfo . bShouldGenerateCode )
{
FileContents + = FString : : Printf ( TEXT ( " GlobalDefaultGameMode= \" /Script/%s.%sGameMode \" " ) LINE_TERMINATOR , * NewProjectName , * NewProjectName ) ;
}
2014-03-14 14:13:41 -04:00
}
if ( WriteOutputFile ( DefaultEngineIniFilename , FileContents , OutFailReason ) )
{
OutCreatedFiles . Add ( DefaultEngineIniFilename ) ;
}
else
{
return false ;
}
}
2014-07-21 09:14:28 -04:00
// DefaultEditor.ini
{
const FString DefaultEditorIniFilename = ProjectConfigPath / TEXT ( " DefaultEditor.ini " ) ;
FString FileContents ;
FileContents + = TEXT ( " [EditoronlyBP] " ) LINE_TERMINATOR ;
FileContents + = TEXT ( " bAllowClassAndBlueprintPinMatching=true " ) LINE_TERMINATOR ;
FileContents + = TEXT ( " bReplaceBlueprintWithClass=true " ) LINE_TERMINATOR ;
FileContents + = TEXT ( " bDontLoadBlueprintOutsideEditor=true " ) LINE_TERMINATOR ;
FileContents + = TEXT ( " bBlueprintIsNotBlueprintType=true " ) LINE_TERMINATOR ;
if ( WriteOutputFile ( DefaultEditorIniFilename , FileContents , OutFailReason ) )
{
OutCreatedFiles . Add ( DefaultEditorIniFilename ) ;
}
else
{
return false ;
}
}
2015-04-22 12:28:12 -04:00
// DefaultGame.ini
{
const FString DefaultGameIniFilename = ProjectConfigPath / TEXT ( " DefaultGame.ini " ) ;
FString FileContents ;
FileContents + = TEXT ( " [/Script/EngineSettings.GeneralProjectSettings] " ) LINE_TERMINATOR ;
FileContents + = TEXT ( " ProjectID= " ) + FGuid : : NewGuid ( ) . ToString ( ) + LINE_TERMINATOR ;
if ( WriteOutputFile ( DefaultGameIniFilename , FileContents , OutFailReason ) )
{
OutCreatedFiles . Add ( DefaultGameIniFilename ) ;
}
else
{
return false ;
}
}
2014-03-14 14:13:41 -04:00
return true ;
}
2014-10-13 11:47:21 -04:00
bool GameProjectUtils : : GenerateBasicSourceCode ( TArray < FString > & OutCreatedFiles , FText & OutFailReason )
{
TArray < FString > StartupModuleNames ;
if ( GameProjectUtils : : GenerateBasicSourceCode ( FPaths : : GameSourceDir ( ) . LeftChop ( 1 ) , FApp : : GetGameName ( ) , FPaths : : GameDir ( ) , StartupModuleNames , OutCreatedFiles , OutFailReason ) )
{
2015-03-17 09:34:18 -04:00
GameProjectUtils : : UpdateProject (
FProjectDescriptorModifier : : CreateLambda (
[ & StartupModuleNames ] ( FProjectDescriptor & Descriptor )
{
return UpdateStartupModuleNames ( Descriptor , & StartupModuleNames ) ;
} ) ) ;
2014-10-13 11:47:21 -04:00
return true ;
}
return false ;
}
2014-09-23 13:55:06 -04:00
bool GameProjectUtils : : GenerateBasicSourceCode ( const FString & NewProjectSourcePath , const FString & NewProjectName , const FString & NewProjectRoot , TArray < FString > & OutGeneratedStartupModuleNames , TArray < FString > & OutCreatedFiles , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
const FString GameModulePath = NewProjectSourcePath / NewProjectName ;
const FString EditorName = NewProjectName + TEXT ( " Editor " ) ;
// MyGame.Build.cs
{
const FString NewBuildFilename = GameModulePath / NewProjectName + TEXT ( " .Build.cs " ) ;
TArray < FString > PublicDependencyModuleNames ;
PublicDependencyModuleNames . Add ( TEXT ( " Core " ) ) ;
PublicDependencyModuleNames . Add ( TEXT ( " CoreUObject " ) ) ;
PublicDependencyModuleNames . Add ( TEXT ( " Engine " ) ) ;
PublicDependencyModuleNames . Add ( TEXT ( " InputCore " ) ) ;
TArray < FString > PrivateDependencyModuleNames ;
if ( GenerateGameModuleBuildFile ( NewBuildFilename , NewProjectName , PublicDependencyModuleNames , PrivateDependencyModuleNames , OutFailReason ) )
{
OutGeneratedStartupModuleNames . Add ( NewProjectName ) ;
OutCreatedFiles . Add ( NewBuildFilename ) ;
}
else
{
return false ;
}
}
// MyGame.Target.cs
{
const FString NewTargetFilename = NewProjectSourcePath / NewProjectName + TEXT ( " .Target.cs " ) ;
TArray < FString > ExtraModuleNames ;
ExtraModuleNames . Add ( NewProjectName ) ;
if ( GenerateGameModuleTargetFile ( NewTargetFilename , NewProjectName , ExtraModuleNames , OutFailReason ) )
{
OutCreatedFiles . Add ( NewTargetFilename ) ;
}
else
{
return false ;
}
}
// MyGameEditor.Target.cs
{
const FString NewTargetFilename = NewProjectSourcePath / EditorName + TEXT ( " .Target.cs " ) ;
// Include the MyGame module...
TArray < FString > ExtraModuleNames ;
ExtraModuleNames . Add ( NewProjectName ) ;
if ( GenerateEditorModuleTargetFile ( NewTargetFilename , EditorName , ExtraModuleNames , OutFailReason ) )
{
OutCreatedFiles . Add ( NewTargetFilename ) ;
}
else
{
return false ;
}
}
// MyGame.h
{
const FString NewHeaderFilename = GameModulePath / NewProjectName + TEXT ( " .h " ) ;
TArray < FString > PublicHeaderIncludes ;
PublicHeaderIncludes . Add ( TEXT ( " Engine.h " ) ) ;
if ( GenerateGameModuleHeaderFile ( NewHeaderFilename , PublicHeaderIncludes , OutFailReason ) )
{
OutCreatedFiles . Add ( NewHeaderFilename ) ;
}
else
{
return false ;
}
}
// MyGame.cpp
{
const FString NewCPPFilename = GameModulePath / NewProjectName + TEXT ( " .cpp " ) ;
if ( GenerateGameModuleCPPFile ( NewCPPFilename , NewProjectName , NewProjectName , OutFailReason ) )
{
OutCreatedFiles . Add ( NewCPPFilename ) ;
}
else
{
return false ;
}
}
return true ;
}
bool GameProjectUtils : : GenerateGameFrameworkSourceCode ( const FString & NewProjectSourcePath , const FString & NewProjectName , TArray < FString > & OutCreatedFiles , FText & OutFailReason )
{
const FString GameModulePath = NewProjectSourcePath / NewProjectName ;
2014-05-16 10:47:37 -04:00
// Used to override the code generation validation since the module we're creating isn't the same as the project we currently have loaded
FModuleContextInfo NewModuleInfo ;
NewModuleInfo . ModuleName = NewProjectName ;
2014-08-04 18:21:05 -04:00
NewModuleInfo . ModuleType = EHostType : : Runtime ;
NewModuleInfo . ModuleSourcePath = FPaths : : ConvertRelativePathToFull ( GameModulePath / " " ) ; // Ensure trailing /
2014-05-16 10:47:37 -04:00
2014-03-14 14:13:41 -04:00
// MyGameGameMode.h
{
const UClass * BaseClass = AGameMode : : StaticClass ( ) ;
2014-05-16 07:26:51 -04:00
const FString NewClassName = NewProjectName + BaseClass - > GetName ( ) ;
const FString NewHeaderFilename = GameModulePath / NewClassName + TEXT ( " .h " ) ;
2014-03-14 14:13:41 -04:00
FString UnusedSyncLocation ;
2014-10-10 04:34:56 -04:00
if ( GenerateClassHeaderFile ( NewHeaderFilename , NewClassName , FNewClassInfo ( BaseClass ) , TArray < FString > ( ) , TEXT ( " " ) , TEXT ( " " ) , UnusedSyncLocation , NewModuleInfo , false , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
OutCreatedFiles . Add ( NewHeaderFilename ) ;
}
else
{
return false ;
}
}
// MyGameGameMode.cpp
{
const UClass * BaseClass = AGameMode : : StaticClass ( ) ;
2014-05-16 07:26:51 -04:00
const FString NewClassName = NewProjectName + BaseClass - > GetName ( ) ;
const FString NewCPPFilename = GameModulePath / NewClassName + TEXT ( " .cpp " ) ;
2014-03-14 14:13:41 -04:00
TArray < FString > PropertyOverrides ;
TArray < FString > AdditionalIncludes ;
2015-01-26 20:13:06 -05:00
FString UnusedSyncLocation ;
2014-03-14 14:13:41 -04:00
2015-01-26 20:13:06 -05:00
if ( GenerateClassCPPFile ( NewCPPFilename , NewClassName , FNewClassInfo ( BaseClass ) , AdditionalIncludes , PropertyOverrides , TEXT ( " " ) , UnusedSyncLocation , NewModuleInfo , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
OutCreatedFiles . Add ( NewCPPFilename ) ;
}
else
{
return false ;
}
}
return true ;
}
2014-12-11 16:39:39 -05:00
bool GameProjectUtils : : BuildCodeProject ( const FString & ProjectFilename )
{
// Build the project while capturing the log output. Passing GWarn to CompileGameProject will allow Slate to display the progress bar.
FStringOutputDevice OutputLog ;
OutputLog . SetAutoEmitLineTerminator ( true ) ;
GLog - > AddOutputDevice ( & OutputLog ) ;
bool bCompileSucceeded = FDesktopPlatformModule : : Get ( ) - > CompileGameProject ( FPaths : : RootDir ( ) , ProjectFilename , GWarn ) ;
GLog - > RemoveOutputDevice ( & OutputLog ) ;
// Try to compile the modules
if ( ! bCompileSucceeded )
{
FText DevEnvName = FSourceCodeNavigation : : GetSuggestedSourceCodeIDE ( true ) ;
TArray < FText > CompileFailedButtons ;
int32 OpenIDEButton = CompileFailedButtons . Add ( FText : : Format ( LOCTEXT ( " CompileFailedOpenIDE " , " Open with {0} " ) , DevEnvName ) ) ;
CompileFailedButtons . Add ( LOCTEXT ( " CompileFailedCancel " , " Cancel " ) ) ;
2015-04-27 15:33:53 -04:00
FText LogText = FText : : FromString ( OutputLog . Replace ( LINE_TERMINATOR , TEXT ( " \n " ) ) . TrimTrailing ( ) ) ;
int32 CompileFailedChoice = SOutputLogDialog : : Open ( LOCTEXT ( " CompileFailedTitle " , " Compile Failed " ) , FText : : Format ( LOCTEXT ( " CompileFailedHeader " , " The project could not be compiled. Would you like to open it in {0}? " ) , DevEnvName ) , LogText , FText : : GetEmpty ( ) , CompileFailedButtons ) ;
2014-12-11 16:39:39 -05:00
FText FailReason ;
if ( CompileFailedChoice = = OpenIDEButton & & ! GameProjectUtils : : OpenCodeIDE ( ProjectFilename , FailReason ) )
{
FMessageDialog : : Open ( EAppMsgType : : Ok , FailReason ) ;
}
}
return bCompileSucceeded ;
}
2015-04-09 11:11:48 -04:00
bool GameProjectUtils : : GenerateCodeProjectFiles ( const FString & ProjectFilename , FText & OutFailReason , FText & OutFailLog )
2014-03-14 14:13:41 -04:00
{
2014-08-22 08:33:51 -04:00
FStringOutputDevice OutputLog ;
OutputLog . SetAutoEmitLineTerminator ( true ) ;
GLog - > AddOutputDevice ( & OutputLog ) ;
bool bHaveProjectFiles = FDesktopPlatformModule : : Get ( ) - > GenerateProjectFiles ( FPaths : : RootDir ( ) , ProjectFilename , GWarn ) ;
GLog - > RemoveOutputDevice ( & OutputLog ) ;
if ( ! bHaveProjectFiles )
2014-03-14 14:13:41 -04:00
{
2015-04-09 11:11:48 -04:00
OutFailReason = LOCTEXT ( " ErrorWhileGeneratingProjectFiles " , " An error occurred while trying to generate project files. " ) ;
OutFailLog = FText : : FromString ( OutputLog ) ;
2014-08-22 08:33:51 -04:00
return false ;
2014-03-14 14:13:41 -04:00
}
2014-08-22 08:33:51 -04:00
return true ;
2014-03-14 14:13:41 -04:00
}
bool GameProjectUtils : : IsStarterContentAvailableForNewProjects ( )
{
TArray < FString > StarterContentFiles ;
GetStarterContentFiles ( StarterContentFiles ) ;
2015-02-09 10:40:24 -05:00
bool bHasStaterContent = StarterContentFiles . FindByPredicate ( [ & ] ( const FString & Str ) { return Str . Contains ( " StarterContent " ) ; } ) ! = nullptr ;
return bHasStaterContent ;
2014-03-14 14:13:41 -04:00
}
2014-10-13 11:47:21 -04:00
TArray < FModuleContextInfo > GameProjectUtils : : GetCurrentProjectModules ( )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
2014-08-04 18:21:05 -04:00
const FProjectDescriptor * const CurrentProject = IProjectManager : : Get ( ) . GetCurrentProject ( ) ;
check ( CurrentProject ) ;
2014-05-16 10:47:37 -04:00
2014-08-04 18:21:05 -04:00
TArray < FModuleContextInfo > RetModuleInfos ;
2014-05-16 10:47:37 -04:00
2014-08-04 18:21:05 -04:00
if ( ! GameProjectUtils : : ProjectHasCodeFiles ( ) | | CurrentProject - > Modules . Num ( ) = = 0 )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
2014-08-04 18:21:05 -04:00
// If this project doesn't currently have any code in it, we need to add a dummy entry for the game
// so that we can still use the class wizard (this module will be created once we add a class)
FModuleContextInfo ModuleInfo ;
ModuleInfo . ModuleName = FApp : : GetGameName ( ) ;
ModuleInfo . ModuleType = EHostType : : Runtime ;
ModuleInfo . ModuleSourcePath = FPaths : : ConvertRelativePathToFull ( FPaths : : GameSourceDir ( ) / ModuleInfo . ModuleName / " " ) ; // Ensure trailing /
RetModuleInfos . Emplace ( ModuleInfo ) ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
}
2014-08-04 18:21:05 -04:00
// Resolve out the paths for each module and add the cut-down into to our output array
for ( const FModuleDescriptor & ModuleDesc : CurrentProject - > Modules )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
2014-08-04 18:21:05 -04:00
FModuleContextInfo ModuleInfo ;
ModuleInfo . ModuleName = ModuleDesc . Name . ToString ( ) ;
ModuleInfo . ModuleType = ModuleDesc . Type ;
// Try and find the .Build.cs file for this module within our currently loaded project's Source directory
FString TmpPath ;
if ( ! FindSourceFileInProject ( ModuleInfo . ModuleName + " .Build.cs " , FPaths : : GameSourceDir ( ) , TmpPath ) )
{
continue ;
}
// Chop the .Build.cs file off the end of the path
ModuleInfo . ModuleSourcePath = FPaths : : GetPath ( TmpPath ) ;
ModuleInfo . ModuleSourcePath = FPaths : : ConvertRelativePathToFull ( ModuleInfo . ModuleSourcePath / " " ) ; // Ensure trailing /
RetModuleInfos . Emplace ( ModuleInfo ) ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
}
2014-08-04 18:21:05 -04:00
return RetModuleInfos ;
}
bool GameProjectUtils : : IsValidSourcePath ( const FString & InPath , const FModuleContextInfo & ModuleInfo , FText * const OutFailReason )
{
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
const FString AbsoluteInPath = FPaths : : ConvertRelativePathToFull ( InPath ) / " " ; // Ensure trailing /
// Validate the path contains no invalid characters
if ( ! FPaths : : ValidatePath ( AbsoluteInPath , OutFailReason ) )
{
return false ;
}
2014-08-04 18:21:05 -04:00
if ( ! AbsoluteInPath . StartsWith ( ModuleInfo . ModuleSourcePath ) )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
if ( OutFailReason )
{
FFormatNamedArguments Args ;
2014-08-04 18:21:05 -04:00
Args . Add ( TEXT ( " ModuleName " ) , FText : : FromString ( ModuleInfo . ModuleName ) ) ;
Args . Add ( TEXT ( " RootSourcePath " ) , FText : : FromString ( ModuleInfo . ModuleSourcePath ) ) ;
* OutFailReason = FText : : Format ( LOCTEXT ( " SourcePathInvalidForModule " , " All source code for '{ModuleName}' must exist within '{RootSourcePath}' " ) , Args ) ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
}
return false ;
}
return true ;
}
2014-08-04 18:21:05 -04:00
bool GameProjectUtils : : CalculateSourcePaths ( const FString & InPath , const FModuleContextInfo & ModuleInfo , FString & OutHeaderPath , FString & OutSourcePath , FText * const OutFailReason )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
const FString AbsoluteInPath = FPaths : : ConvertRelativePathToFull ( InPath ) / " " ; // Ensure trailing /
OutHeaderPath = AbsoluteInPath ;
OutSourcePath = AbsoluteInPath ;
2014-05-14 06:47:25 -04:00
EClassLocation ClassPathLocation = EClassLocation : : UserDefined ;
2014-08-04 18:21:05 -04:00
if ( ! GetClassLocation ( InPath , ModuleInfo , ClassPathLocation , OutFailReason ) )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
return false ;
}
2014-08-04 18:21:05 -04:00
const FString RootPath = ModuleInfo . ModuleSourcePath ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
const FString PublicPath = RootPath / " Public " / " " ; // Ensure trailing /
const FString PrivatePath = RootPath / " Private " / " " ; // Ensure trailing /
2014-05-14 06:47:25 -04:00
const FString ClassesPath = RootPath / " Classes " / " " ; // Ensure trailing /
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
// The root path must exist; we will allow the creation of sub-folders, but not the module root!
2014-04-23 19:33:00 -04:00
// We ignore this check if the project doesn't already have source code in it, as the module folder won't yet have been created
const bool bHasCodeFiles = GameProjectUtils : : ProjectHasCodeFiles ( ) ;
if ( ! IFileManager : : Get ( ) . DirectoryExists ( * RootPath ) & & bHasCodeFiles )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
if ( OutFailReason )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ModuleSourcePath " ) , FText : : FromString ( RootPath ) ) ;
* OutFailReason = FText : : Format ( LOCTEXT ( " SourcePathMissingModuleRoot " , " The specified module path does not exist on disk: {ModuleSourcePath} " ) , Args ) ;
}
return false ;
}
2014-05-14 06:47:25 -04:00
// The rules for placing header files are as follows:
// 1) If InPath is the source root, and GetClassLocation has said the class header should be in the Public folder, put it in the Public folder
// 2) Otherwise, just place the header at InPath (the default set above)
if ( AbsoluteInPath = = RootPath )
{
OutHeaderPath = ( ClassPathLocation = = EClassLocation : : Public ) ? PublicPath : AbsoluteInPath ;
}
// The rules for placing source files are as follows:
// 1) If InPath is the source root, and GetClassLocation has said the class header should be in the Public folder, put the source file in the Private folder
// 2) If InPath is contained within the Public or Classes folder of this module, place it in the equivalent path in the Private folder
// 3) Otherwise, just place the source file at InPath (the default set above)
if ( AbsoluteInPath = = RootPath )
{
OutSourcePath = ( ClassPathLocation = = EClassLocation : : Public ) ? PrivatePath : AbsoluteInPath ;
}
else if ( ClassPathLocation = = EClassLocation : : Public )
{
OutSourcePath = AbsoluteInPath . Replace ( * PublicPath , * PrivatePath ) ;
}
else if ( ClassPathLocation = = EClassLocation : : Classes )
{
OutSourcePath = AbsoluteInPath . Replace ( * ClassesPath , * PrivatePath ) ;
}
return ! OutHeaderPath . IsEmpty ( ) & & ! OutSourcePath . IsEmpty ( ) ;
}
2014-08-04 18:21:05 -04:00
bool GameProjectUtils : : GetClassLocation ( const FString & InPath , const FModuleContextInfo & ModuleInfo , EClassLocation & OutClassLocation , FText * const OutFailReason )
2014-05-14 06:47:25 -04:00
{
const FString AbsoluteInPath = FPaths : : ConvertRelativePathToFull ( InPath ) / " " ; // Ensure trailing /
OutClassLocation = EClassLocation : : UserDefined ;
2014-08-04 18:21:05 -04:00
if ( ! IsValidSourcePath ( InPath , ModuleInfo , OutFailReason ) )
2014-05-14 06:47:25 -04:00
{
return false ;
}
2014-08-04 18:21:05 -04:00
const FString RootPath = ModuleInfo . ModuleSourcePath ;
2014-05-14 06:47:25 -04:00
const FString PublicPath = RootPath / " Public " / " " ; // Ensure trailing /
const FString PrivatePath = RootPath / " Private " / " " ; // Ensure trailing /
const FString ClassesPath = RootPath / " Classes " / " " ; // Ensure trailing /
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
// If either the Public or Private path exists, and we're in the root, force the header/source file to use one of these folders
const bool bPublicPathExists = IFileManager : : Get ( ) . DirectoryExists ( * PublicPath ) ;
const bool bPrivatePathExists = IFileManager : : Get ( ) . DirectoryExists ( * PrivatePath ) ;
const bool bForceInternalPath = AbsoluteInPath = = RootPath & & ( bPublicPathExists | | bPrivatePathExists ) ;
if ( AbsoluteInPath = = RootPath )
{
2014-05-14 06:47:25 -04:00
OutClassLocation = ( bPublicPathExists | | bForceInternalPath ) ? EClassLocation : : Public : EClassLocation : : UserDefined ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
}
else if ( AbsoluteInPath . StartsWith ( PublicPath ) )
{
2014-05-14 06:47:25 -04:00
OutClassLocation = EClassLocation : : Public ;
}
else if ( AbsoluteInPath . StartsWith ( PrivatePath ) )
{
OutClassLocation = EClassLocation : : Private ;
}
else if ( AbsoluteInPath . StartsWith ( ClassesPath ) )
{
OutClassLocation = EClassLocation : : Classes ;
}
else
{
OutClassLocation = EClassLocation : : UserDefined ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
}
2014-05-14 06:47:25 -04:00
return true ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
}
2014-09-26 11:29:33 -04:00
GameProjectUtils : : EProjectDuplicateResult GameProjectUtils : : DuplicateProjectForUpgrade ( const FString & InProjectFile , FString & OutNewProjectFile )
2014-05-13 18:23:53 -04:00
{
IPlatformFile & PlatformFile = FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) ;
// Get the directory part of the project name
FString OldDirectoryName = FPaths : : GetPath ( InProjectFile ) ;
FPaths : : NormalizeDirectoryName ( OldDirectoryName ) ;
FString NewDirectoryName = OldDirectoryName ;
// Strip off any previous version number from the project name
for ( int32 LastSpace ; NewDirectoryName . FindLastChar ( ' ' , LastSpace ) ; )
{
const TCHAR * End = * NewDirectoryName + LastSpace + 1 ;
2014-05-29 17:25:19 -04:00
if ( End [ 0 ] ! = ' 4 ' | | End [ 1 ] ! = ' . ' | | ! FChar : : IsDigit ( End [ 2 ] ) )
2014-05-16 09:12:15 -04:00
{
break ;
}
2014-05-29 17:25:19 -04:00
End + = 3 ;
while ( FChar : : IsDigit ( * End ) )
2014-05-13 18:23:53 -04:00
{
End + + ;
}
2014-05-16 09:12:15 -04:00
2014-05-29 17:25:19 -04:00
if ( * End ! = 0 )
2014-05-16 09:12:15 -04:00
{
break ;
}
NewDirectoryName = NewDirectoryName . Left ( LastSpace ) . TrimTrailing ( ) ;
2014-05-13 18:23:53 -04:00
}
// Append the new version number
2014-05-29 17:25:19 -04:00
NewDirectoryName + = FString : : Printf ( TEXT ( " %s " ) , * GEngineVersion . ToString ( EVersionComponent : : Minor ) ) ;
2014-05-13 18:23:53 -04:00
// Find a directory name that doesn't exist
FString BaseDirectoryName = NewDirectoryName ;
for ( int32 Idx = 2 ; IFileManager : : Get ( ) . DirectoryExists ( * NewDirectoryName ) ; Idx + + )
{
2014-05-29 17:25:19 -04:00
NewDirectoryName = FString : : Printf ( TEXT ( " %s - %d " ) , * BaseDirectoryName , Idx ) ;
2014-05-13 18:23:53 -04:00
}
// Find all the root directory names
TArray < FString > RootDirectoryNames ;
IFileManager : : Get ( ) . FindFiles ( RootDirectoryNames , * ( OldDirectoryName / TEXT ( " * " ) ) , false , true ) ;
// Find all the source directories
TArray < FString > SourceDirectories ;
SourceDirectories . Add ( OldDirectoryName ) ;
for ( int32 Idx = 0 ; Idx < RootDirectoryNames . Num ( ) ; Idx + + )
{
if ( RootDirectoryNames [ Idx ] ! = TEXT ( " Binaries " ) & & RootDirectoryNames [ Idx ] ! = TEXT ( " Intermediate " ) & & RootDirectoryNames [ Idx ] ! = TEXT ( " Saved " ) )
{
FString SourceDirectory = OldDirectoryName / RootDirectoryNames [ Idx ] ;
SourceDirectories . Add ( SourceDirectory ) ;
IFileManager : : Get ( ) . FindFilesRecursive ( SourceDirectories , * SourceDirectory , TEXT ( " * " ) , false , true , false ) ;
}
}
// Find all the source files
TArray < FString > SourceFiles ;
for ( int32 Idx = 0 ; Idx < SourceDirectories . Num ( ) ; Idx + + )
{
TArray < FString > SourceNames ;
IFileManager : : Get ( ) . FindFiles ( SourceNames , * ( SourceDirectories [ Idx ] / TEXT ( " * " ) ) , true , false ) ;
for ( int32 NameIdx = 0 ; NameIdx < SourceNames . Num ( ) ; NameIdx + + )
{
SourceFiles . Add ( SourceDirectories [ Idx ] / SourceNames [ NameIdx ] ) ;
}
}
// Copy everything
bool bCopySucceeded = true ;
2014-09-26 11:29:33 -04:00
bool bUserCanceled = false ;
GWarn - > BeginSlowTask ( LOCTEXT ( " CreatingCopyOfProject " , " Creating copy of project... " ) , true , true ) ;
2014-05-13 18:23:53 -04:00
for ( int32 Idx = 0 ; Idx < SourceDirectories . Num ( ) & & bCopySucceeded ; Idx + + )
{
FString TargetDirectory = NewDirectoryName + SourceDirectories [ Idx ] . Mid ( OldDirectoryName . Len ( ) ) ;
2014-09-26 11:29:33 -04:00
bUserCanceled = GWarn - > ReceivedUserCancel ( ) ;
bCopySucceeded = ! bUserCanceled & & PlatformFile . CreateDirectory ( * TargetDirectory ) ;
2014-05-13 18:23:53 -04:00
GWarn - > UpdateProgress ( Idx + 1 , SourceDirectories . Num ( ) + SourceFiles . Num ( ) ) ;
}
for ( int32 Idx = 0 ; Idx < SourceFiles . Num ( ) & & bCopySucceeded ; Idx + + )
{
FString TargetFile = NewDirectoryName + SourceFiles [ Idx ] . Mid ( OldDirectoryName . Len ( ) ) ;
2014-09-26 11:29:33 -04:00
bUserCanceled = GWarn - > ReceivedUserCancel ( ) ;
bCopySucceeded = ! bUserCanceled & & PlatformFile . CopyFile ( * TargetFile , * SourceFiles [ Idx ] ) ;
2014-05-13 18:23:53 -04:00
GWarn - > UpdateProgress ( SourceDirectories . Num ( ) + Idx + 1 , SourceDirectories . Num ( ) + SourceFiles . Num ( ) ) ;
}
GWarn - > EndSlowTask ( ) ;
2014-09-26 11:29:33 -04:00
// Wipe the directory if the user canceled or we couldn't update
2014-05-13 18:23:53 -04:00
if ( ! bCopySucceeded )
{
PlatformFile . DeleteDirectoryRecursively ( * NewDirectoryName ) ;
2014-09-26 11:29:33 -04:00
if ( bUserCanceled )
{
return EProjectDuplicateResult : : UserCanceled ;
}
else
{
return EProjectDuplicateResult : : Failed ;
}
2014-05-13 18:23:53 -04:00
}
// Otherwise fixup the output project filename
OutNewProjectFile = NewDirectoryName / FPaths : : GetCleanFilename ( InProjectFile ) ;
2014-09-26 11:29:33 -04:00
return EProjectDuplicateResult : : Succeeded ;
2014-05-13 18:23:53 -04:00
}
2014-05-29 17:37:19 -04:00
void GameProjectUtils : : UpdateSupportedTargetPlatforms ( const FName & InPlatformName , const bool bIsSupported )
{
const FString & ProjectFilename = FPaths : : GetProjectFilePath ( ) ;
if ( ! ProjectFilename . IsEmpty ( ) )
{
// First attempt to check out the file if SCC is enabled
if ( ISourceControlModule : : Get ( ) . IsEnabled ( ) )
{
FText UnusedFailReason ;
CheckoutGameProjectFile ( ProjectFilename , UnusedFailReason ) ;
}
// Second make sure the file is writable
if ( FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . IsReadOnly ( * ProjectFilename ) )
{
FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . SetReadOnly ( * ProjectFilename , false ) ;
}
IProjectManager : : Get ( ) . UpdateSupportedTargetPlatformsForCurrentProject ( InPlatformName , bIsSupported ) ;
}
}
2014-06-05 12:13:44 -04:00
void GameProjectUtils : : ClearSupportedTargetPlatforms ( )
{
const FString & ProjectFilename = FPaths : : GetProjectFilePath ( ) ;
if ( ! ProjectFilename . IsEmpty ( ) )
{
// First attempt to check out the file if SCC is enabled
if ( ISourceControlModule : : Get ( ) . IsEnabled ( ) )
{
FText UnusedFailReason ;
CheckoutGameProjectFile ( ProjectFilename , UnusedFailReason ) ;
}
// Second make sure the file is writable
if ( FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . IsReadOnly ( * ProjectFilename ) )
{
FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . SetReadOnly ( * ProjectFilename , false ) ;
}
IProjectManager : : Get ( ) . ClearSupportedTargetPlatformsForCurrentProject ( ) ;
}
}
2014-03-14 14:13:41 -04:00
bool GameProjectUtils : : ReadTemplateFile ( const FString & TemplateFileName , FString & OutFileContents , FText & OutFailReason )
{
const FString FullFileName = FPaths : : EngineContentDir ( ) / TEXT ( " Editor " ) / TEXT ( " Templates " ) / TemplateFileName ;
if ( FFileHelper : : LoadFileToString ( OutFileContents , * FullFileName ) )
{
return true ;
}
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " FullFileName " ) , FText : : FromString ( FullFileName ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToReadTemplateFile " , " Failed to read template file \" {FullFileName} \" " ) , Args ) ;
return false ;
}
bool GameProjectUtils : : WriteOutputFile ( const FString & OutputFilename , const FString & OutputFileContents , FText & OutFailReason )
{
if ( FFileHelper : : SaveStringToFile ( OutputFileContents , * OutputFilename ) )
{
return true ;
}
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " OutputFilename " ) , FText : : FromString ( OutputFilename ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToWriteOutputFile " , " Failed to write output file \" {OutputFilename} \" . Perhaps the file is Read-Only? " ) , Args ) ;
return false ;
}
FString GameProjectUtils : : MakeCopyrightLine ( )
{
2014-08-14 18:39:11 -04:00
const FString CopyrightNotice = GetDefault < UGeneralProjectSettings > ( ) - > CopyrightNotice ;
if ( ! CopyrightNotice . IsEmpty ( ) )
2014-04-02 18:09:23 -04:00
{
2014-08-14 18:39:11 -04:00
return FString ( TEXT ( " // " ) ) + CopyrightNotice ;
}
else
{
return FString ( ) ;
2014-04-02 18:09:23 -04:00
}
2014-03-14 14:13:41 -04:00
}
FString GameProjectUtils : : MakeCommaDelimitedList ( const TArray < FString > & InList , bool bPlaceQuotesAroundEveryElement )
{
FString ReturnString ;
for ( auto ListIt = InList . CreateConstIterator ( ) ; ListIt ; + + ListIt )
{
FString ElementStr ;
if ( bPlaceQuotesAroundEveryElement )
{
ElementStr = FString : : Printf ( TEXT ( " \" %s \" " ) , * * ListIt ) ;
}
else
{
ElementStr = * ListIt ;
}
if ( ReturnString . Len ( ) > 0 )
{
// If this is not the first item in the list, prepend with a comma
ElementStr = FString : : Printf ( TEXT ( " , %s " ) , * ElementStr ) ;
}
ReturnString + = ElementStr ;
}
return ReturnString ;
}
FString GameProjectUtils : : MakeIncludeList ( const TArray < FString > & InList )
{
FString ReturnString ;
for ( auto ListIt = InList . CreateConstIterator ( ) ; ListIt ; + + ListIt )
{
ReturnString + = FString : : Printf ( TEXT ( " #include \" %s \" " ) LINE_TERMINATOR , * * ListIt ) ;
}
return ReturnString ;
}
2014-10-13 11:47:21 -04:00
FString GameProjectUtils : : DetermineModuleIncludePath ( const FModuleContextInfo & ModuleInfo , const FString & FileRelativeTo )
{
FString ModuleIncludePath ;
if ( FindSourceFileInProject ( ModuleInfo . ModuleName + " .h " , ModuleInfo . ModuleSourcePath , ModuleIncludePath ) )
{
// Work out where the module header is;
// if it's Public then we can include it without any path since all Public and Classes folders are on the include path
// if it's located elsewhere, then we'll need to include it relative to the module source root as we can't guarantee
// that other folders are on the include paths
EClassLocation ModuleLocation ;
if ( GetClassLocation ( ModuleIncludePath , ModuleInfo , ModuleLocation ) )
{
if ( ModuleLocation = = EClassLocation : : Public | | ModuleLocation = = EClassLocation : : Classes )
{
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
}
else
{
// If the path to our new class is the same as the path to the module, we can include it directly
const FString ModulePath = FPaths : : ConvertRelativePathToFull ( FPaths : : GetPath ( ModuleIncludePath ) ) ;
const FString ClassPath = FPaths : : ConvertRelativePathToFull ( FPaths : : GetPath ( FileRelativeTo ) ) ;
if ( ModulePath = = ClassPath )
{
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
}
else
{
// Updates ModuleIncludePath internally
if ( ! FPaths : : MakePathRelativeTo ( ModuleIncludePath , * ModuleInfo . ModuleSourcePath ) )
{
// Failed; just assume we can include it without any relative path
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
}
}
}
}
else
{
// Failed; just assume we can include it without any relative path
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
}
}
else
{
// This could potentially fail when generating new projects if the module file hasn't yet been created; just assume we can include it without any relative path
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
}
return ModuleIncludePath ;
}
2014-10-10 04:34:56 -04:00
/**
* Generates UObject class constructor definition with property overrides .
*
* @ param Out String to assign generated constructor to .
* @ param PrefixedClassName Prefixed class name for which we generate the constructor .
* @ param PropertyOverridesStr String with property overrides in the constructor .
* @ param OutFailReason Template read function failure reason .
*
* @ returns True on success . False otherwise .
*/
bool GenerateConstructorDefinition ( FString & Out , const FString & PrefixedClassName , const FString & PropertyOverridesStr , FText & OutFailReason )
{
FString Template ;
if ( ! GameProjectUtils : : ReadTemplateFile ( TEXT ( " UObjectClassConstructorDefinition.template " ) , Template , OutFailReason ) )
{
return false ;
}
Out = Template . Replace ( TEXT ( " %PREFIXED_CLASS_NAME% " ) , * PrefixedClassName , ESearchCase : : CaseSensitive ) ;
Out = Out . Replace ( TEXT ( " %PROPERTY_OVERRIDES% " ) , * PropertyOverridesStr , ESearchCase : : CaseSensitive ) ;
return true ;
}
/**
* Generates UObject class constructor declaration .
*
* @ param Out String to assign generated constructor to .
* @ param PrefixedClassName Prefixed class name for which we generate the constructor .
* @ param OutFailReason Template read function failure reason .
*
* @ returns True on success . False otherwise .
*/
bool GenerateConstructorDeclaration ( FString & Out , const FString & PrefixedClassName , FText & OutFailReason )
{
FString Template ;
if ( ! GameProjectUtils : : ReadTemplateFile ( TEXT ( " UObjectClassConstructorDeclaration.template " ) , Template , OutFailReason ) )
{
return false ;
}
Out = Template . Replace ( TEXT ( " %PREFIXED_CLASS_NAME% " ) , * PrefixedClassName , ESearchCase : : CaseSensitive ) ;
return true ;
}
bool GameProjectUtils : : GenerateClassHeaderFile ( const FString & NewHeaderFileName , const FString UnPrefixedClassName , const FNewClassInfo ParentClassInfo , const TArray < FString > & ClassSpecifierList , const FString & ClassProperties , const FString & ClassFunctionDeclarations , FString & OutSyncLocation , const FModuleContextInfo & ModuleInfo , bool bDeclareConstructor , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
FString Template ;
2014-05-16 07:26:51 -04:00
if ( ! ReadTemplateFile ( ParentClassInfo . GetHeaderTemplateFilename ( ) , Template , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
return false ;
}
2014-05-16 07:26:51 -04:00
const FString ClassPrefix = ParentClassInfo . GetClassPrefixCPP ( ) ;
2014-03-14 14:13:41 -04:00
const FString PrefixedClassName = ClassPrefix + UnPrefixedClassName ;
2014-05-16 07:26:51 -04:00
const FString PrefixedBaseClassName = ClassPrefix + ParentClassInfo . GetClassNameCPP ( ) ;
2014-03-14 14:13:41 -04:00
FString BaseClassIncludeDirective ;
2014-05-16 07:26:51 -04:00
FString BaseClassIncludePath ;
if ( ParentClassInfo . GetIncludePath ( BaseClassIncludePath ) )
2014-03-14 14:13:41 -04:00
{
2014-05-16 07:26:51 -04:00
BaseClassIncludeDirective = FString : : Printf ( LINE_TERMINATOR TEXT ( " #include \" %s \" " ) , * BaseClassIncludePath ) ;
2014-03-14 14:13:41 -04:00
}
2014-05-14 06:47:25 -04:00
FString ModuleAPIMacro ;
{
EClassLocation ClassPathLocation = EClassLocation : : UserDefined ;
2014-08-04 18:21:05 -04:00
if ( GetClassLocation ( NewHeaderFileName , ModuleInfo , ClassPathLocation ) )
2014-05-14 06:47:25 -04:00
{
2014-05-16 10:47:37 -04:00
// If this class isn't Private, make sure and include the API macro so it can be linked within other modules
if ( ClassPathLocation ! = EClassLocation : : Private )
{
2014-08-04 18:21:05 -04:00
ModuleAPIMacro = ModuleInfo . ModuleName . ToUpper ( ) + " _API " ; // include a trailing space for the template formatting
2014-05-16 10:47:37 -04:00
}
2014-05-14 06:47:25 -04:00
}
}
2014-10-10 04:34:56 -04:00
FString EventualConstructorDeclaration ;
if ( bDeclareConstructor )
{
if ( ! GenerateConstructorDeclaration ( EventualConstructorDeclaration , PrefixedClassName , OutFailReason ) )
{
return false ;
}
}
2014-05-16 07:26:51 -04:00
// Not all of these will exist in every class template
2014-03-14 14:13:41 -04:00
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
2014-05-16 07:26:51 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %UNPREFIXED_CLASS_NAME% " ) , * UnPrefixedClassName , ESearchCase : : CaseSensitive ) ;
2014-05-14 06:47:25 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %CLASS_MODULE_API_MACRO% " ) , * ModuleAPIMacro , ESearchCase : : CaseSensitive ) ;
2014-03-14 14:13:41 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %UCLASS_SPECIFIER_LIST% " ) , * MakeCommaDelimitedList ( ClassSpecifierList , false ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PREFIXED_CLASS_NAME% " ) , * PrefixedClassName , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PREFIXED_BASE_CLASS_NAME% " ) , * PrefixedBaseClassName , ESearchCase : : CaseSensitive ) ;
2014-10-10 04:34:56 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %EVENTUAL_CONSTRUCTOR_DECLARATION% " ) , * EventualConstructorDeclaration , ESearchCase : : CaseSensitive ) ;
2014-03-14 14:13:41 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %CLASS_PROPERTIES% " ) , * ClassProperties , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %CLASS_FUNCTION_DECLARATIONS% " ) , * ClassFunctionDeclarations , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %BASE_CLASS_INCLUDE_DIRECTIVE% " ) , * BaseClassIncludeDirective , ESearchCase : : CaseSensitive ) ;
2015-01-26 20:13:06 -05:00
HarvestCursorSyncLocation ( FinalOutput , OutSyncLocation ) ;
2014-03-14 14:13:41 -04:00
return WriteOutputFile ( NewHeaderFileName , FinalOutput , OutFailReason ) ;
}
2015-01-26 20:13:06 -05:00
bool GameProjectUtils : : GenerateClassCPPFile ( const FString & NewCPPFileName , const FString UnPrefixedClassName , const FNewClassInfo ParentClassInfo , const TArray < FString > & AdditionalIncludes , const TArray < FString > & PropertyOverrides , const FString & AdditionalMemberDefinitions , FString & OutSyncLocation , const FModuleContextInfo & ModuleInfo , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
FString Template ;
2014-05-16 07:26:51 -04:00
if ( ! ReadTemplateFile ( ParentClassInfo . GetSourceTemplateFilename ( ) , Template , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
return false ;
}
2014-05-16 07:26:51 -04:00
const FString ClassPrefix = ParentClassInfo . GetClassPrefixCPP ( ) ;
const FString PrefixedClassName = ClassPrefix + UnPrefixedClassName ;
const FString PrefixedBaseClassName = ClassPrefix + ParentClassInfo . GetClassNameCPP ( ) ;
EClassLocation ClassPathLocation = EClassLocation : : UserDefined ;
2014-08-04 18:21:05 -04:00
if ( ! GetClassLocation ( NewCPPFileName , ModuleInfo , ClassPathLocation , & OutFailReason ) )
2014-05-16 10:47:37 -04:00
{
return false ;
}
2014-05-16 07:26:51 -04:00
2014-03-14 14:13:41 -04:00
FString AdditionalIncludesStr ;
for ( int32 IncludeIdx = 0 ; IncludeIdx < AdditionalIncludes . Num ( ) ; + + IncludeIdx )
{
if ( IncludeIdx > 0 )
{
AdditionalIncludesStr + = LINE_TERMINATOR ;
}
AdditionalIncludesStr + = FString : : Printf ( TEXT ( " #include \" %s \" " ) , * AdditionalIncludes [ IncludeIdx ] ) ;
}
FString PropertyOverridesStr ;
for ( int32 OverrideIdx = 0 ; OverrideIdx < PropertyOverrides . Num ( ) ; + + OverrideIdx )
{
if ( OverrideIdx > 0 )
{
PropertyOverridesStr + = LINE_TERMINATOR ;
}
PropertyOverridesStr + = TEXT ( " \t " ) ;
PropertyOverridesStr + = * PropertyOverrides [ OverrideIdx ] ;
}
2014-06-18 06:45:20 -04:00
// Calculate the correct include path for the module header
2014-10-13 11:47:21 -04:00
const FString ModuleIncludePath = DetermineModuleIncludePath ( ModuleInfo , NewCPPFileName ) ;
2014-06-18 06:45:20 -04:00
2014-10-10 04:34:56 -04:00
FString EventualConstructorDefinition ;
if ( PropertyOverrides . Num ( ) ! = 0 )
{
if ( ! GenerateConstructorDefinition ( EventualConstructorDefinition , PrefixedClassName , PropertyOverridesStr , OutFailReason ) )
{
return false ;
}
}
2014-05-16 07:26:51 -04:00
// Not all of these will exist in every class template
2014-03-14 14:13:41 -04:00
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
2014-05-16 07:26:51 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %UNPREFIXED_CLASS_NAME% " ) , * UnPrefixedClassName , ESearchCase : : CaseSensitive ) ;
2014-08-04 18:21:05 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %MODULE_NAME% " ) , * ModuleInfo . ModuleName , ESearchCase : : CaseSensitive ) ;
2014-06-18 06:45:20 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %MODULE_INCLUDE_PATH% " ) , * ModuleIncludePath , ESearchCase : : CaseSensitive ) ;
2014-03-14 14:13:41 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %PREFIXED_CLASS_NAME% " ) , * PrefixedClassName , ESearchCase : : CaseSensitive ) ;
2014-10-10 04:34:56 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %EVENTUAL_CONSTRUCTOR_DEFINITION% " ) , * EventualConstructorDefinition , ESearchCase : : CaseSensitive ) ;
2014-03-14 14:13:41 -04:00
FinalOutput = FinalOutput . Replace ( TEXT ( " %ADDITIONAL_MEMBER_DEFINITIONS% " ) , * AdditionalMemberDefinitions , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %ADDITIONAL_INCLUDE_DIRECTIVES% " ) , * AdditionalIncludesStr , ESearchCase : : CaseSensitive ) ;
2015-01-26 20:13:06 -05:00
HarvestCursorSyncLocation ( FinalOutput , OutSyncLocation ) ;
2014-03-14 14:13:41 -04:00
return WriteOutputFile ( NewCPPFileName , FinalOutput , OutFailReason ) ;
}
bool GameProjectUtils : : GenerateGameModuleBuildFile ( const FString & NewBuildFileName , const FString & ModuleName , const TArray < FString > & PublicDependencyModuleNames , const TArray < FString > & PrivateDependencyModuleNames , FText & OutFailReason )
{
FString Template ;
if ( ! ReadTemplateFile ( TEXT ( " GameModule.Build.cs.template " ) , Template , OutFailReason ) )
{
return false ;
}
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PUBLIC_DEPENDENCY_MODULE_NAMES% " ) , * MakeCommaDelimitedList ( PublicDependencyModuleNames ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PRIVATE_DEPENDENCY_MODULE_NAMES% " ) , * MakeCommaDelimitedList ( PrivateDependencyModuleNames ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %MODULE_NAME% " ) , * ModuleName , ESearchCase : : CaseSensitive ) ;
return WriteOutputFile ( NewBuildFileName , FinalOutput , OutFailReason ) ;
}
bool GameProjectUtils : : GenerateGameModuleTargetFile ( const FString & NewBuildFileName , const FString & ModuleName , const TArray < FString > & ExtraModuleNames , FText & OutFailReason )
{
FString Template ;
if ( ! ReadTemplateFile ( TEXT ( " Stub.Target.cs.template " ) , Template , OutFailReason ) )
{
return false ;
}
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %EXTRA_MODULE_NAMES% " ) , * MakeCommaDelimitedList ( ExtraModuleNames ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %MODULE_NAME% " ) , * ModuleName , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %TARGET_TYPE% " ) , TEXT ( " Game " ) , ESearchCase : : CaseSensitive ) ;
return WriteOutputFile ( NewBuildFileName , FinalOutput , OutFailReason ) ;
}
bool GameProjectUtils : : GenerateEditorModuleBuildFile ( const FString & NewBuildFileName , const FString & ModuleName , const TArray < FString > & PublicDependencyModuleNames , const TArray < FString > & PrivateDependencyModuleNames , FText & OutFailReason )
{
FString Template ;
if ( ! ReadTemplateFile ( TEXT ( " EditorModule.Build.cs.template " ) , Template , OutFailReason ) )
{
return false ;
}
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PUBLIC_DEPENDENCY_MODULE_NAMES% " ) , * MakeCommaDelimitedList ( PublicDependencyModuleNames ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PRIVATE_DEPENDENCY_MODULE_NAMES% " ) , * MakeCommaDelimitedList ( PrivateDependencyModuleNames ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %MODULE_NAME% " ) , * ModuleName , ESearchCase : : CaseSensitive ) ;
return WriteOutputFile ( NewBuildFileName , FinalOutput , OutFailReason ) ;
}
bool GameProjectUtils : : GenerateEditorModuleTargetFile ( const FString & NewBuildFileName , const FString & ModuleName , const TArray < FString > & ExtraModuleNames , FText & OutFailReason )
{
FString Template ;
if ( ! ReadTemplateFile ( TEXT ( " Stub.Target.cs.template " ) , Template , OutFailReason ) )
{
return false ;
}
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %EXTRA_MODULE_NAMES% " ) , * MakeCommaDelimitedList ( ExtraModuleNames ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %MODULE_NAME% " ) , * ModuleName , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %TARGET_TYPE% " ) , TEXT ( " Editor " ) , ESearchCase : : CaseSensitive ) ;
return WriteOutputFile ( NewBuildFileName , FinalOutput , OutFailReason ) ;
}
bool GameProjectUtils : : GenerateGameModuleCPPFile ( const FString & NewBuildFileName , const FString & ModuleName , const FString & GameName , FText & OutFailReason )
{
FString Template ;
if ( ! ReadTemplateFile ( TEXT ( " GameModule.cpp.template " ) , Template , OutFailReason ) )
{
return false ;
}
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %MODULE_NAME% " ) , * ModuleName , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %GAME_NAME% " ) , * GameName , ESearchCase : : CaseSensitive ) ;
return WriteOutputFile ( NewBuildFileName , FinalOutput , OutFailReason ) ;
}
bool GameProjectUtils : : GenerateGameModuleHeaderFile ( const FString & NewBuildFileName , const TArray < FString > & PublicHeaderIncludes , FText & OutFailReason )
{
FString Template ;
if ( ! ReadTemplateFile ( TEXT ( " GameModule.h.template " ) , Template , OutFailReason ) )
{
return false ;
}
FString FinalOutput = Template . Replace ( TEXT ( " %COPYRIGHT_LINE% " ) , * MakeCopyrightLine ( ) , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PUBLIC_HEADER_INCLUDES% " ) , * MakeIncludeList ( PublicHeaderIncludes ) , ESearchCase : : CaseSensitive ) ;
return WriteOutputFile ( NewBuildFileName , FinalOutput , OutFailReason ) ;
}
void GameProjectUtils : : OnUpdateProjectConfirm ( )
{
2015-03-17 09:34:18 -04:00
UpdateProject ( ) ;
2014-03-14 14:13:41 -04:00
}
2015-03-17 09:34:18 -04:00
void GameProjectUtils : : UpdateProject ( const FProjectDescriptorModifier & Modifier )
{
UpdateProject_Impl ( & Modifier ) ;
}
void GameProjectUtils : : UpdateProject ( )
{
UpdateProject_Impl ( nullptr ) ;
}
void GameProjectUtils : : UpdateProject_Impl ( const FProjectDescriptorModifier * Modifier )
2014-03-14 14:13:41 -04:00
{
const FString & ProjectFilename = FPaths : : GetProjectFilePath ( ) ;
const FString & ShortFilename = FPaths : : GetCleanFilename ( ProjectFilename ) ;
FText FailReason ;
FText UpdateMessage ;
SNotificationItem : : ECompletionState NewCompletionState ;
2015-03-17 09:34:18 -04:00
if ( UpdateGameProjectFile_Impl ( ProjectFilename , FDesktopPlatformModule : : Get ( ) - > GetCurrentEngineIdentifier ( ) , Modifier , FailReason ) )
2014-03-14 14:13:41 -04:00
{
// The project was updated successfully.
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ShortFilename " ) , FText : : FromString ( ShortFilename ) ) ;
UpdateMessage = FText : : Format ( LOCTEXT ( " ProjectFileUpdateComplete " , " {ShortFilename} was successfully updated. " ) , Args ) ;
NewCompletionState = SNotificationItem : : CS_Success ;
}
else
{
// The user chose to update, but the update failed. Notify the user.
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ShortFilename " ) , FText : : FromString ( ShortFilename ) ) ;
Args . Add ( TEXT ( " FailReason " ) , FailReason ) ;
UpdateMessage = FText : : Format ( LOCTEXT ( " ProjectFileUpdateFailed " , " {ShortFilename} failed to update. {FailReason} " ) , Args ) ;
NewCompletionState = SNotificationItem : : CS_Fail ;
}
if ( UpdateGameProjectNotification . IsValid ( ) )
{
UpdateGameProjectNotification . Pin ( ) - > SetCompletionState ( NewCompletionState ) ;
UpdateGameProjectNotification . Pin ( ) - > SetText ( UpdateMessage ) ;
UpdateGameProjectNotification . Pin ( ) - > ExpireAndFadeout ( ) ;
UpdateGameProjectNotification . Reset ( ) ;
}
}
2015-03-17 09:34:18 -04:00
void GameProjectUtils : : UpdateProject ( const TArray < FString > * StartupModuleNames )
{
UpdateProject (
FProjectDescriptorModifier : : CreateLambda (
[ StartupModuleNames ] ( FProjectDescriptor & Desc )
{
if ( StartupModuleNames ! = nullptr )
{
return UpdateStartupModuleNames ( Desc , StartupModuleNames ) ;
}
return false ;
} ) ) ;
}
2014-03-14 14:13:41 -04:00
void GameProjectUtils : : OnUpdateProjectCancel ( )
{
if ( UpdateGameProjectNotification . IsValid ( ) )
{
UpdateGameProjectNotification . Pin ( ) - > SetCompletionState ( SNotificationItem : : CS_None ) ;
UpdateGameProjectNotification . Pin ( ) - > ExpireAndFadeout ( ) ;
UpdateGameProjectNotification . Reset ( ) ;
}
}
2014-07-22 15:58:02 -04:00
void GameProjectUtils : : TryMakeProjectFileWriteable ( const FString & ProjectFile )
2014-03-14 14:13:41 -04:00
{
// First attempt to check out the file if SCC is enabled
if ( ISourceControlModule : : Get ( ) . IsEnabled ( ) )
{
2014-06-27 15:23:15 -04:00
FText FailReason ;
2014-07-22 15:58:02 -04:00
GameProjectUtils : : CheckoutGameProjectFile ( ProjectFile , FailReason ) ;
2014-03-14 14:13:41 -04:00
}
2014-06-27 15:23:15 -04:00
2014-07-22 15:58:02 -04:00
// Check if it's writable
if ( FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . IsReadOnly ( * ProjectFile ) )
2014-03-14 14:13:41 -04:00
{
2014-07-22 15:58:02 -04:00
FText ShouldMakeProjectWriteable = LOCTEXT ( " ShouldMakeProjectWriteable_Message " , " '{ProjectFilename}' is read-only and cannot be updated. Would you like to make it writeable? " ) ;
2014-06-27 15:23:15 -04:00
FFormatNamedArguments Arguments ;
2014-07-22 15:58:02 -04:00
Arguments . Add ( TEXT ( " ProjectFilename " ) , FText : : FromString ( ProjectFile ) ) ;
2014-06-27 15:23:15 -04:00
if ( FMessageDialog : : Open ( EAppMsgType : : YesNo , FText : : Format ( ShouldMakeProjectWriteable , Arguments ) ) = = EAppReturnType : : Yes )
2014-03-14 14:13:41 -04:00
{
2014-07-22 15:58:02 -04:00
FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . SetReadOnly ( * ProjectFile , false ) ;
2014-03-14 14:13:41 -04:00
}
}
2014-06-27 15:23:15 -04:00
}
2015-03-17 09:34:18 -04:00
bool GameProjectUtils : : UpdateGameProjectFile ( const FString & ProjectFile , const FString & EngineIdentifier , const FProjectDescriptorModifier & Modifier , FText & OutFailReason )
{
return UpdateGameProjectFile_Impl ( ProjectFile , EngineIdentifier , & Modifier , OutFailReason ) ;
}
bool GameProjectUtils : : UpdateGameProjectFile ( const FString & ProjectFile , const FString & EngineIdentifier , FText & OutFailReason )
{
return UpdateGameProjectFile_Impl ( ProjectFile , EngineIdentifier , nullptr , OutFailReason ) ;
}
bool GameProjectUtils : : UpdateGameProjectFile_Impl ( const FString & ProjectFile , const FString & EngineIdentifier , const FProjectDescriptorModifier * Modifier , FText & OutFailReason )
2014-06-27 15:23:15 -04:00
{
// Make sure we can write to the project file
2014-07-22 15:58:02 -04:00
TryMakeProjectFileWriteable ( ProjectFile ) ;
2014-03-14 14:13:41 -04:00
2014-07-22 15:58:02 -04:00
// Load the descriptor
FProjectDescriptor Descriptor ;
if ( Descriptor . Load ( ProjectFile , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
2015-03-17 09:34:18 -04:00
if ( Modifier & & Modifier - > IsBound ( ) & & ! Modifier - > Execute ( Descriptor ) )
2014-07-22 15:58:02 -04:00
{
2015-03-17 09:34:18 -04:00
// If modifier returns false it means that we want to drop changes.
return true ;
2014-07-22 15:58:02 -04:00
}
// Update file on disk
2015-01-09 08:08:13 -05:00
return Descriptor . Save ( ProjectFile , OutFailReason ) & & FDesktopPlatformModule : : Get ( ) - > SetEngineIdentifierForProject ( ProjectFile , EngineIdentifier ) ;
2014-07-22 15:58:02 -04:00
}
return false ;
2014-03-14 14:13:41 -04:00
}
2015-03-17 09:34:18 -04:00
bool GameProjectUtils : : UpdateGameProjectFile ( const FString & ProjectFilename , const FString & EngineIdentifier , const TArray < FString > * StartupModuleNames , FText & OutFailReason )
{
return UpdateGameProjectFile ( ProjectFilename , EngineIdentifier ,
FProjectDescriptorModifier : : CreateLambda (
[ StartupModuleNames ] ( FProjectDescriptor & Desc )
{
if ( StartupModuleNames ! = nullptr )
{
return UpdateStartupModuleNames ( Desc , StartupModuleNames ) ;
}
return false ;
}
) , OutFailReason ) ;
}
2014-03-14 14:13:41 -04:00
bool GameProjectUtils : : CheckoutGameProjectFile ( const FString & ProjectFilename , FText & OutFailReason )
{
if ( ! ensure ( ProjectFilename . Len ( ) ) )
{
OutFailReason = LOCTEXT ( " NoProjectFilename " , " The project filename was not specified. " ) ;
return false ;
}
if ( ! ISourceControlModule : : Get ( ) . IsEnabled ( ) )
{
OutFailReason = LOCTEXT ( " SCCDisabled " , " Source control is not enabled. Enable source control in the preferences menu. " ) ;
return false ;
}
FString AbsoluteFilename = FPaths : : ConvertRelativePathToFull ( ProjectFilename ) ;
ISourceControlProvider & SourceControlProvider = ISourceControlModule : : Get ( ) . GetProvider ( ) ;
FSourceControlStatePtr SourceControlState = SourceControlProvider . GetState ( AbsoluteFilename , EStateCacheUsage : : ForceUpdate ) ;
TArray < FString > FilesToBeCheckedOut ;
FilesToBeCheckedOut . Add ( AbsoluteFilename ) ;
bool bSuccessfullyCheckedOut = false ;
OutFailReason = LOCTEXT ( " SCCStateInvalid " , " Could not determine source control state. " ) ;
if ( SourceControlState . IsValid ( ) )
{
if ( SourceControlState - > IsCheckedOut ( ) | | SourceControlState - > IsAdded ( ) | | ! SourceControlState - > IsSourceControlled ( ) )
{
// Already checked out or opened for add... or not in the depot at all
bSuccessfullyCheckedOut = true ;
}
else if ( SourceControlState - > CanCheckout ( ) | | SourceControlState - > IsCheckedOutOther ( ) )
{
bSuccessfullyCheckedOut = ( SourceControlProvider . Execute ( ISourceControlOperation : : Create < FCheckOut > ( ) , FilesToBeCheckedOut ) = = ECommandResult : : Succeeded ) ;
if ( ! bSuccessfullyCheckedOut )
{
OutFailReason = LOCTEXT ( " SCCCheckoutFailed " , " Failed to check out the project file. " ) ;
}
}
else if ( ! SourceControlState - > IsCurrent ( ) )
{
OutFailReason = LOCTEXT ( " SCCNotCurrent " , " The project file is not at head revision. " ) ;
}
}
return bSuccessfullyCheckedOut ;
}
FString GameProjectUtils : : GetDefaultProjectTemplateFilename ( )
{
return TEXT ( " " ) ;
}
2014-08-22 11:35:01 -04:00
void GameProjectUtils : : GetProjectCodeFilenames ( TArray < FString > & OutProjectCodeFilenames )
{
IFileManager : : Get ( ) . FindFilesRecursive ( OutProjectCodeFilenames , * FPaths : : GameSourceDir ( ) , TEXT ( " *.h " ) , true , false , false ) ;
IFileManager : : Get ( ) . FindFilesRecursive ( OutProjectCodeFilenames , * FPaths : : GameSourceDir ( ) , TEXT ( " *.cpp " ) , true , false , false ) ;
}
2014-03-14 14:13:41 -04:00
int32 GameProjectUtils : : GetProjectCodeFileCount ( )
{
TArray < FString > Filenames ;
2014-08-22 11:35:01 -04:00
GetProjectCodeFilenames ( Filenames ) ;
2014-03-14 14:13:41 -04:00
return Filenames . Num ( ) ;
}
2014-08-22 11:35:01 -04:00
void GameProjectUtils : : GetProjectSourceDirectoryInfo ( int32 & OutNumCodeFiles , int64 & OutDirectorySize )
{
TArray < FString > Filenames ;
GetProjectCodeFilenames ( Filenames ) ;
OutNumCodeFiles = Filenames . Num ( ) ;
OutDirectorySize = 0 ;
for ( const auto & filename : Filenames )
{
OutDirectorySize + = IFileManager : : Get ( ) . FileSize ( * filename ) ;
}
}
2014-03-14 14:13:41 -04:00
bool GameProjectUtils : : ProjectHasCodeFiles ( )
{
return GameProjectUtils : : GetProjectCodeFileCount ( ) > 0 ;
}
2015-03-17 09:34:18 -04:00
TArray < FString > GameProjectUtils : : GetRequiredAdditionalDependencies ( const FNewClassInfo & ClassInfo )
{
TArray < FString > Out ;
switch ( ClassInfo . ClassType )
{
case FNewClassInfo : : EClassType : : SlateWidget :
case FNewClassInfo : : EClassType : : SlateWidgetStyle :
Out . Reserve ( 2 ) ;
Out . Add ( TEXT ( " Slate " ) ) ;
Out . Add ( TEXT ( " SlateCore " ) ) ;
break ;
case FNewClassInfo : : EClassType : : UObject :
auto ClassPackageName = ClassInfo . BaseClass - > GetOutermost ( ) - > GetFName ( ) . ToString ( ) ;
checkf ( ClassPackageName . StartsWith ( TEXT ( " /Script/ " ) ) , TEXT ( " Class outermost should start with /Script/ " ) ) ;
Out . Add ( ClassPackageName . Mid ( 8 ) ) ; // Skip the /Script/ prefix.
break ;
}
return Out ;
}
2015-04-17 10:02:43 -04:00
GameProjectUtils : : EAddCodeToProjectResult GameProjectUtils : : AddCodeToProject_Internal ( const FString & NewClassName , const FString & NewClassPath , const FModuleContextInfo & ModuleInfo , const FNewClassInfo ParentClassInfo , const TSet < FString > & DisallowedHeaderNames , FString & OutHeaderFilePath , FString & OutCppFilePath , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
2014-05-16 07:26:51 -04:00
if ( ! ParentClassInfo . IsSet ( ) )
2014-03-14 14:13:41 -04:00
{
OutFailReason = LOCTEXT ( " NoParentClass " , " You must specify a parent class " ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : InvalidInput ;
2014-03-14 14:13:41 -04:00
}
2014-05-16 07:26:51 -04:00
const FString CleanClassName = ParentClassInfo . GetCleanClassName ( NewClassName ) ;
const FString FinalClassName = ParentClassInfo . GetFinalClassName ( NewClassName ) ;
2014-11-12 11:31:40 -05:00
if ( ! IsValidClassNameForCreation ( FinalClassName , ModuleInfo , DisallowedHeaderNames , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : InvalidInput ;
2014-03-14 14:13:41 -04:00
}
if ( ! FApp : : HasGameName ( ) )
{
2014-05-12 08:38:56 -04:00
OutFailReason = LOCTEXT ( " AddCodeToProject_NoGameName " , " You can not add code because you have not loaded a project. " ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToAddCode ;
2014-03-14 14:13:41 -04:00
}
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
FString NewHeaderPath ;
FString NewCppPath ;
2014-08-04 18:21:05 -04:00
if ( ! CalculateSourcePaths ( NewClassPath , ModuleInfo , NewHeaderPath , NewCppPath , & OutFailReason ) )
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
{
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToAddCode ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
}
2015-02-09 06:17:18 -05:00
FScopedSlowTask SlowTask ( 7 , LOCTEXT ( " AddingCodeToProject " , " Adding code to project... " ) ) ;
2014-10-08 04:42:34 -04:00
SlowTask . MakeDialog ( ) ;
SlowTask . EnterProgressFrame ( ) ;
2015-03-17 09:34:18 -04:00
auto RequiredDependencies = GetRequiredAdditionalDependencies ( ParentClassInfo ) ;
RequiredDependencies . Remove ( ModuleInfo . ModuleName ) ;
// Update project file if needed.
auto bUpdateProjectModules = false ;
2014-12-11 06:03:58 -05:00
2014-03-14 14:13:41 -04:00
// If the project does not already contain code, add the primary game module
TArray < FString > CreatedFiles ;
2015-03-17 09:34:18 -04:00
TArray < FString > StartupModuleNames ;
2015-02-06 11:27:24 -05:00
const bool bProjectHadCodeFiles = ProjectHasCodeFiles ( ) ;
if ( ! bProjectHadCodeFiles )
2014-03-14 14:13:41 -04:00
{
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
// We always add the basic source code to the root directory, not the potential sub-directory provided by NewClassPath
const FString SourceDir = FPaths : : GameSourceDir ( ) . LeftChop ( 1 ) ; // Trim the trailing /
// Assuming the game name is the same as the primary game module name
2014-05-16 07:26:51 -04:00
const FString GameModuleName = FApp : : GetGameName ( ) ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
2014-09-23 13:55:06 -04:00
if ( GenerateBasicSourceCode ( SourceDir , GameModuleName , FPaths : : GameDir ( ) , StartupModuleNames , CreatedFiles , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
2015-03-17 09:34:18 -04:00
bUpdateProjectModules = true ;
2014-03-14 14:13:41 -04:00
}
else
{
DeleteCreatedFiles ( SourceDir , CreatedFiles ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToAddCode ;
2014-03-14 14:13:41 -04:00
}
}
2015-03-17 09:34:18 -04:00
if ( RequiredDependencies . Num ( ) > 0 | | bUpdateProjectModules )
{
UpdateProject (
FProjectDescriptorModifier : : CreateLambda (
[ & StartupModuleNames , & RequiredDependencies , & ModuleInfo , bUpdateProjectModules ] ( FProjectDescriptor & Descriptor )
{
bool bNeedsUpdate = false ;
bNeedsUpdate | = UpdateStartupModuleNames ( Descriptor , bUpdateProjectModules ? & StartupModuleNames : nullptr ) ;
bNeedsUpdate | = UpdateRequiredAdditionalDependencies ( Descriptor , RequiredDependencies , ModuleInfo . ModuleName ) ;
return bNeedsUpdate ;
} ) ) ;
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Class Header File
2014-05-16 07:26:51 -04:00
const FString NewHeaderFilename = NewHeaderPath / ParentClassInfo . GetHeaderFilename ( NewClassName ) ;
2014-03-14 14:13:41 -04:00
{
2015-01-26 20:13:06 -05:00
FString UnusedSyncLocation ;
if ( GenerateClassHeaderFile ( NewHeaderFilename , CleanClassName , ParentClassInfo , TArray < FString > ( ) , TEXT ( " " ) , TEXT ( " " ) , UnusedSyncLocation , ModuleInfo , false , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
CreatedFiles . Add ( NewHeaderFilename ) ;
}
else
{
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
DeleteCreatedFiles ( NewHeaderPath , CreatedFiles ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToAddCode ;
2014-03-14 14:13:41 -04:00
}
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Class CPP file
2014-05-16 07:26:51 -04:00
const FString NewCppFilename = NewCppPath / ParentClassInfo . GetSourceFilename ( NewClassName ) ;
2014-03-14 14:13:41 -04:00
{
2015-01-26 20:13:06 -05:00
FString UnusedSyncLocation ;
if ( GenerateClassCPPFile ( NewCppFilename , CleanClassName , ParentClassInfo , TArray < FString > ( ) , TArray < FString > ( ) , TEXT ( " " ) , UnusedSyncLocation , ModuleInfo , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
CreatedFiles . Add ( NewCppFilename ) ;
2014-03-14 14:13:41 -04:00
}
else
{
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
DeleteCreatedFiles ( NewCppPath , CreatedFiles ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToAddCode ;
2014-03-14 14:13:41 -04:00
}
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2015-02-06 11:27:24 -05:00
TArray < FString > CreatedFilesForExternalAppRead ;
CreatedFilesForExternalAppRead . Reserve ( CreatedFiles . Num ( ) ) ;
for ( const FString & CreatedFile : CreatedFiles )
2014-03-14 14:13:41 -04:00
{
2015-02-06 11:27:24 -05:00
CreatedFilesForExternalAppRead . Add ( IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForRead ( * CreatedFile ) ) ;
}
2015-02-16 07:53:59 -05:00
bool bGenerateProjectFiles = true ;
2015-02-06 11:27:24 -05:00
// First see if we can avoid a full generation by adding the new files to an already open project
if ( bProjectHadCodeFiles & & FSourceCodeNavigation : : AddSourceFiles ( CreatedFilesForExternalAppRead ) )
{
2015-02-16 07:53:59 -05:00
// We successfully added the new files to the solution, but we still need to run UBT with -gather to update any UBT makefiles
2015-03-17 09:34:18 -04:00
if ( FDesktopPlatformModule : : Get ( ) - > InvalidateMakefiles ( FPaths : : RootDir ( ) , FPaths : : GetProjectFilePath ( ) , GWarn ) )
2015-02-16 07:53:59 -05:00
{
// We managed the gather, so we can skip running the full generate
bGenerateProjectFiles = false ;
}
2015-02-06 11:27:24 -05:00
}
2015-02-16 07:53:59 -05:00
if ( bGenerateProjectFiles )
2015-02-06 11:27:24 -05:00
{
// Generate project files if we happen to be using a project file.
if ( ! FDesktopPlatformModule : : Get ( ) - > GenerateProjectFiles ( FPaths : : RootDir ( ) , FPaths : : GetProjectFilePath ( ) , GWarn ) )
{
OutFailReason = LOCTEXT ( " FailedToGenerateProjectFiles " , " Failed to generate project files. " ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToHotReload ;
2015-02-06 11:27:24 -05:00
}
2014-03-14 14:13:41 -04:00
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( ) ;
2014-03-14 14:13:41 -04:00
// Mark the files for add in SCC
ISourceControlProvider & SourceControlProvider = ISourceControlModule : : Get ( ) . GetProvider ( ) ;
if ( ISourceControlModule : : Get ( ) . IsEnabled ( ) & & SourceControlProvider . IsAvailable ( ) )
{
2015-02-06 11:27:24 -05:00
SourceControlProvider . Execute ( ISourceControlOperation : : Create < FMarkForAdd > ( ) , CreatedFilesForExternalAppRead ) ;
2014-03-14 14:13:41 -04:00
}
2015-01-26 20:16:08 -05:00
SlowTask . EnterProgressFrame ( 1.0f , LOCTEXT ( " CompilingCPlusPlusCode " , " Compiling new C++ code. Please wait... " ) ) ;
2014-03-14 14:13:41 -04:00
OutHeaderFilePath = NewHeaderFilename ;
#ttp 322244 - LIVE: Feature Request: During "Add Code to Project", allow user to select path where files go
#proj UE4
#branch UE4
#summary You can now choose where to place a class added via the New Class Wizard
#extra This tries to be smart about your placement if you have Public and Private folders for your project.
- By default the header would go into Public, and the source file would go into Private.
- If you select the Public/Classes folder for the path, the source file will still go into Private.
- If you have a sub-path, eg) /Public/MyStuff/MyClass.h, this will be mirrored in the placement of the source file, eg) /Private/MyStuff/MyClass.cpp
#extra If you're not using Public or Private folders it will just place the source at whatever path you specified.
#extra It will verify that your source code is going to a valid module folder for your game, and also allows matching of modules that start with your game name, eg) MyGame, MyGameEditor.
#reviewedby Thomas.Sarkanen, Max.Preussner
[CL 2046528 by Jamie Dale in Main branch]
2014-04-23 18:50:08 -04:00
OutCppFilePath = NewCppFilename ;
2014-03-14 14:13:41 -04:00
2015-02-06 11:27:24 -05:00
if ( ! bProjectHadCodeFiles )
2014-12-11 06:03:58 -05:00
{
// This is the first time we add code to this project so compile its game DLL
const FString GameModuleName = FApp : : GetGameName ( ) ;
2015-01-16 15:39:47 -05:00
check ( ModuleInfo . ModuleName = = GameModuleName ) ;
2014-12-11 06:03:58 -05:00
IHotReloadInterface & HotReloadSupport = FModuleManager : : LoadModuleChecked < IHotReloadInterface > ( " HotReload " ) ;
const bool bReloadAfterCompiling = true ;
const bool bForceCodeProject = true ;
const bool bFailIfGeneratedCodeChanges = false ;
if ( ! HotReloadSupport . RecompileModule ( * GameModuleName , bReloadAfterCompiling , * GWarn , bFailIfGeneratedCodeChanges , bForceCodeProject ) )
{
OutFailReason = LOCTEXT ( " FailedToCompileNewGameModule " , " Failed to compile newly created game module. " ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToHotReload ;
2014-12-11 06:03:58 -05:00
}
2015-01-16 15:39:47 -05:00
// Notify that we've created a brand new module
FSourceCodeNavigation : : AccessOnNewModuleAdded ( ) . Broadcast ( * GameModuleName ) ;
}
2015-04-20 10:12:55 -04:00
else if ( GetDefault < UEditorPerProjectUserSettings > ( ) - > bAutomaticallyHotReloadNewClasses )
2015-01-16 15:39:47 -05:00
{
FModuleStatus ModuleStatus ;
const FName ModuleFName = * ModuleInfo . ModuleName ;
if ( ensure ( FModuleManager : : Get ( ) . QueryModule ( ModuleFName , ModuleStatus ) ) )
{
// Compile the module that the class was added to so that the newly added class with appear in the Content Browser
TArray < UPackage * > PackagesToRebind ;
if ( ModuleStatus . bIsLoaded )
{
const bool bIsHotReloadable = FModuleManager : : Get ( ) . DoesLoadedModuleHaveUObjects ( ModuleFName ) ;
if ( bIsHotReloadable )
{
// Is there a UPackage with the same name as this module?
const FString PotentialPackageName = FString ( TEXT ( " /Script/ " ) ) + ModuleInfo . ModuleName ;
UPackage * Package = FindPackage ( nullptr , * PotentialPackageName ) ;
if ( Package )
{
PackagesToRebind . Add ( Package ) ;
}
}
}
IHotReloadInterface & HotReloadSupport = FModuleManager : : LoadModuleChecked < IHotReloadInterface > ( " HotReload " ) ;
if ( PackagesToRebind . Num ( ) > 0 )
{
// Perform a hot reload
const bool bWaitForCompletion = true ;
2015-01-26 20:16:08 -05:00
ECompilationResult : : Type CompilationResult = HotReloadSupport . RebindPackages ( PackagesToRebind , TArray < FName > ( ) , bWaitForCompletion , * GWarn ) ;
if ( CompilationResult ! = ECompilationResult : : Succeeded & & CompilationResult ! = ECompilationResult : : UpToDate )
{
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToHotReloadModuleFmt " , " Failed to automatically hot reload the '{0}' module. " ) , FText : : FromString ( ModuleInfo . ModuleName ) ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToHotReload ;
2015-01-26 20:16:08 -05:00
}
2015-01-16 15:39:47 -05:00
}
else
{
// Perform a regular unload, then reload
const bool bReloadAfterRecompile = true ;
const bool bForceCodeProject = false ;
const bool bFailIfGeneratedCodeChanges = true ;
if ( ! HotReloadSupport . RecompileModule ( ModuleFName , bReloadAfterRecompile , * GWarn , bFailIfGeneratedCodeChanges , bForceCodeProject ) )
{
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToCompileModuleFmt " , " Failed to automatically compile the '{0}' module. " ) , FText : : FromString ( ModuleInfo . ModuleName ) ) ;
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : FailedToHotReload ;
2015-01-16 15:39:47 -05:00
}
}
}
2014-12-11 06:03:58 -05:00
}
2015-04-17 10:02:43 -04:00
return EAddCodeToProjectResult : : Succeeded ;
2014-03-14 14:13:41 -04:00
}
2014-08-04 18:21:05 -04:00
bool GameProjectUtils : : FindSourceFileInProject ( const FString & InFilename , const FString & InSearchPath , FString & OutPath )
2014-05-14 16:05:52 -04:00
{
2014-06-18 06:45:20 -04:00
TArray < FString > Filenames ;
2015-02-19 09:46:42 -05:00
IFileManager : : Get ( ) . FindFilesRecursive ( Filenames , * InSearchPath , * InFilename , true , false , false ) ;
2014-06-18 06:45:20 -04:00
if ( Filenames . Num ( ) )
2014-05-14 16:05:52 -04:00
{
2014-06-18 06:45:20 -04:00
// Assume it's the first match (we should really only find a single file with a given name within a project anyway)
OutPath = Filenames [ 0 ] ;
return true ;
2014-05-14 16:05:52 -04:00
}
2014-06-18 06:45:20 -04:00
return false ;
2014-05-14 16:05:52 -04:00
}
2015-01-26 20:13:06 -05:00
void GameProjectUtils : : HarvestCursorSyncLocation ( FString & FinalOutput , FString & OutSyncLocation )
{
OutSyncLocation . Empty ( ) ;
// Determine the cursor focus location if this file will by synced after creation
TArray < FString > Lines ;
2015-03-02 15:51:37 -05:00
FinalOutput . ParseIntoArray ( Lines , TEXT ( " \n " ) , false ) ;
2015-01-26 20:13:06 -05:00
for ( int32 LineIdx = 0 ; LineIdx < Lines . Num ( ) ; + + LineIdx )
{
const FString & Line = Lines [ LineIdx ] ;
int32 CharLoc = Line . Find ( TEXT ( " %CURSORFOCUSLOCATION% " ) ) ;
if ( CharLoc ! = INDEX_NONE )
{
// Found the sync marker
OutSyncLocation = FString : : Printf ( TEXT ( " %d:%d " ) , LineIdx + 1 , CharLoc + 1 ) ;
break ;
}
}
// If we did not find the sync location, just sync to the top of the file
if ( OutSyncLocation . IsEmpty ( ) )
{
OutSyncLocation = TEXT ( " 1:1 " ) ;
}
// Now remove the cursor focus marker
FinalOutput = FinalOutput . Replace ( TEXT ( " %CURSORFOCUSLOCATION% " ) , TEXT ( " " ) , ESearchCase : : CaseSensitive ) ;
}
2015-02-09 10:40:24 -05:00
bool GameProjectUtils : : InsertFeaturePacksIntoINIFile ( const FProjectInformation & InProjectInfo , FText & OutFailReason )
{
const FString ProjectName = FPaths : : GetBaseFilename ( InProjectInfo . ProjectFilename ) ;
const FString TemplateName = FPaths : : GetBaseFilename ( InProjectInfo . TemplateFile ) ;
const FString SrcFolder = FPaths : : GetPath ( InProjectInfo . TemplateFile ) ;
const FString DestFolder = FPaths : : GetPath ( InProjectInfo . ProjectFilename ) ;
const FString ProjectConfigPath = DestFolder / TEXT ( " Config " ) ;
const FString IniFilename = ProjectConfigPath / TEXT ( " DefaultGame.ini " ) ;
TArray < FString > PackList ;
// First the starter content
if ( InProjectInfo . bCopyStarterContent )
{
FString StarterPack ;
if ( InProjectInfo . TargetedHardware = = EHardwareClass : : Mobile )
{
StarterPack = TEXT ( " InsertPack=(PackSource= \" MobileStarterContent.upack \" ,PackName= \" StarterContent \" ) " ) ;
}
else
{
StarterPack = TEXT ( " InsertPack=(PackSource= \" StarterContent.upack \" ,PackName= \" StarterContent \" ) " ) ;
}
PackList . Add ( StarterPack ) ;
}
// Now any packs specified in the template def.
UTemplateProjectDefs * TemplateDefs = LoadTemplateDefs ( SrcFolder ) ;
if ( TemplateDefs ! = NULL )
{
for ( int32 iPack = 0 ; iPack < TemplateDefs - > PacksToInclude . Num ( ) ; + + iPack )
{
PackList . Add ( TemplateDefs - > PacksToInclude [ iPack ] ) ;
}
}
if ( PackList . Num ( ) ! = 0 )
{
2015-04-27 15:47:43 -04:00
FString FileOutput ;
if ( FPaths : : FileExists ( IniFilename ) & & ! FFileHelper : : LoadFileToString ( FileOutput , * IniFilename ) )
{
OutFailReason = LOCTEXT ( " FailedToReadIni " , " Could not read INI file to insert feature packs " ) ;
return false ;
}
FileOutput + = LINE_TERMINATOR ;
FileOutput + = TEXT ( " [StartupActions] " ) ;
2015-02-09 10:40:24 -05:00
FileOutput + = LINE_TERMINATOR ;
FileOutput + = TEXT ( " bAddPacks=True " ) ;
FileOutput + = LINE_TERMINATOR ;
for ( int32 iLine = 0 ; iLine < PackList . Num ( ) ; + + iLine )
{
FileOutput + = PackList [ iLine ] + LINE_TERMINATOR ;
}
2015-04-27 15:47:43 -04:00
if ( ! FFileHelper : : SaveStringToFile ( FileOutput , * IniFilename ) )
2015-02-09 10:40:24 -05:00
{
2015-04-27 15:47:43 -04:00
OutFailReason = LOCTEXT ( " FailedToWriteIni " , " Could not write INI file to insert feature packs " ) ;
return false ;
2015-02-09 10:40:24 -05:00
}
}
2015-04-27 15:47:43 -04:00
return true ;
2015-02-09 10:40:24 -05:00
}
2014-03-14 14:13:41 -04:00
# undef LOCTEXT_NAMESPACE