2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
# include "GameProjectGenerationPrivatePCH.h"
# 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-06-10 11:23:42 -04:00
# include "UProjectInfo.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
2014-05-16 07:26:51 -04:00
FString GameProjectUtils : : FNewClassInfo : : GetClassName ( ) const
{
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 ( " " ) ;
}
FString GameProjectUtils : : FNewClassInfo : : GetClassDescription ( ) const
{
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 ( " " ) ;
}
const FSlateBrush * GameProjectUtils : : FNewClassInfo : : GetClassIcon ( ) const
{
// Safe to do even if BaseClass is null, since FindIconForClass will return the default icon
return FClassIconFinder : : FindIconForClass ( BaseClass ) ;
}
FString GameProjectUtils : : FNewClassInfo : : GetClassPrefixCPP ( ) const
{
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 ( " " ) ;
}
FString GameProjectUtils : : FNewClassInfo : : GetClassNameCPP ( ) const
{
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 ( " " ) ;
}
FString GameProjectUtils : : FNewClassInfo : : GetCleanClassName ( const FString & ClassName ) const
{
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 ;
}
FString GameProjectUtils : : FNewClassInfo : : GetFinalClassName ( const FString & ClassName ) const
{
const FString CleanClassName = GetCleanClassName ( ClassName ) ;
switch ( ClassType )
{
case EClassType : : SlateWidgetStyle :
return FString : : Printf ( TEXT ( " %sWidgetStyle " ) , * CleanClassName ) ;
default :
break ;
}
return CleanClassName ;
}
bool GameProjectUtils : : FNewClassInfo : : GetIncludePath ( FString & OutIncludePath ) const
{
switch ( ClassType )
{
case EClassType : : UObject :
if ( BaseClass & & BaseClass - > HasMetaData ( TEXT ( " IncludePath " ) ) )
{
OutIncludePath = BaseClass - > GetMetaData ( TEXT ( " IncludePath " ) ) ;
return true ;
}
break ;
default :
break ;
}
return false ;
}
FString GameProjectUtils : : FNewClassInfo : : GetHeaderFilename ( const FString & ClassName ) const
{
const FString HeaderFilename = GetFinalClassName ( ClassName ) + TEXT ( " .h " ) ;
switch ( ClassType )
{
case EClassType : : SlateWidget :
return TEXT ( " S " ) + HeaderFilename ;
default :
break ;
}
return HeaderFilename ;
}
FString GameProjectUtils : : FNewClassInfo : : GetSourceFilename ( const FString & ClassName ) const
{
const FString SourceFilename = GetFinalClassName ( ClassName ) + TEXT ( " .cpp " ) ;
switch ( ClassType )
{
case EClassType : : SlateWidget :
return TEXT ( " S " ) + SourceFilename ;
default :
break ;
}
return SourceFilename ;
}
FString GameProjectUtils : : FNewClassInfo : : GetHeaderTemplateFilename ( ) const
{
switch ( ClassType )
{
case EClassType : : UObject :
return TEXT ( " UObjectClass.h.template " ) ;
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 ( " " ) ;
}
FString GameProjectUtils : : FNewClassInfo : : GetSourceTemplateFilename ( ) const
{
switch ( ClassType )
{
case EClassType : : UObject :
return TEXT ( " UObjectClass.cpp.template " ) ;
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 ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectPathTooLong " , " A projects path must not be longer than {MaxProjectPathLength} characters. " ) , Args ) ;
return false ;
}
if ( FPaths : : GetExtension ( ProjectFile ) ! = IProjectManager : : GetProjectFileExtension ( ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ProjectFileExtension " ) , FText : : FromString ( IProjectManager : : GetProjectFileExtension ( ) ) ) ;
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 ( " ProjectFileAlreadyExists " , " {ProjectFile} already exists. " ) , Args ) ;
return false ;
}
if ( FPaths : : ConvertRelativePathToFull ( FPaths : : GetPath ( ProjectFile ) ) . StartsWith ( FPaths : : ConvertRelativePathToFull ( FPaths : : EngineDir ( ) ) ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ProjectFile " ) , FText : : FromString ( ProjectFile ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectFileCannotBeUnderEngineFolder " , " {ProjectFile} cannot be saved under the Engine folder. Create the project in a different directory. " ) , Args ) ;
return false ;
}
if ( AnyProjectFilesExistInFolder ( FPaths : : GetPath ( ProjectFile ) ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ProjectFileExtension " ) , FText : : FromString ( IProjectManager : : GetProjectFileExtension ( ) ) ) ;
Args . Add ( TEXT ( " ProjectFilePath " ) , FText : : FromString ( FPaths : : GetPath ( ProjectFile ) ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " AProjectFileAlreadyExistsAtLoction " , " Another .{ProjectFileExtension} file already exists in {ProjectFilePath} " ) , Args ) ;
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 ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " ProjectPathTooLong " , " A projects path must not be longer than {MaxProjectPathLength} characters. " ) , Args ) ;
return false ;
}
if ( FPaths : : GetExtension ( ProjectFile ) ! = IProjectManager : : GetProjectFileExtension ( ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ProjectFileExtension " ) , FText : : FromString ( IProjectManager : : GetProjectFileExtension ( ) ) ) ;
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
STUBBED ( " Linux solution filename " ) ;
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 )
{
FString const SrcFolder = FPaths : : StarterContentDir ( ) ;
FString const ContentFolder = SrcFolder / TEXT ( " Content " ) ;
2014-04-23 20:10:59 -04:00
// only copying /Content
2014-03-14 14:13:41 -04:00
IFileManager : : Get ( ) . FindFilesRecursive ( OutFilenames , * ContentFolder , TEXT ( " * " ) , /*Files=*/ true , /*Directories=*/ false ) ;
}
bool GameProjectUtils : : CopyStarterContent ( const FString & DestProjectFolder , FText & OutFailReason )
{
FString const SrcFolder = FPaths : : StarterContentDir ( ) ;
TArray < FString > FilesToCopy ;
GetStarterContentFiles ( FilesToCopy ) ;
TArray < FString > CreatedFiles ;
for ( FString SrcFilename : FilesToCopy )
{
// Update the slow task dialog
const bool bAllowNewSlowTask = false ;
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " SrcFilename " ) , FText : : FromString ( FPaths : : GetCleanFilename ( SrcFilename ) ) ) ;
2014-06-09 15:41:37 -04:00
FScopedSlowTask SlowTaskMessage ( FText : : Format ( LOCTEXT ( " CreatingProjectStatus_CopyingFile " , " Copying File {SrcFilename}... " ) , Args ) , bAllowNewSlowTask ) ;
2014-03-14 14:13:41 -04:00
FString FileRelPath = FPaths : : GetPath ( SrcFilename ) ;
FPaths : : MakePathRelativeTo ( FileRelPath , * SrcFolder ) ;
// Perform the copy. For file collisions, leave existing file.
const FString DestFilename = DestProjectFolder + TEXT ( " / " ) + FileRelPath + TEXT ( " / " ) + FPaths : : GetCleanFilename ( SrcFilename ) ;
if ( ! FPaths : : FileExists ( DestFilename ) )
{
if ( IFileManager : : Get ( ) . Copy ( * DestFilename , * SrcFilename , false ) = = COPY_OK )
{
CreatedFiles . Add ( DestFilename ) ;
}
else
{
2014-03-15 01:14:25 -04:00
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 ) ;
2014-03-14 14:13:41 -04:00
DeleteCreatedFiles ( DestProjectFolder , CreatedFiles ) ;
return false ;
}
}
}
return true ;
}
bool GameProjectUtils : : CreateProject ( const FString & NewProjectFile , const FString & TemplateFile , bool bShouldGenerateCode , bool bCopyStarterContent , FText & OutFailReason )
{
if ( ! IsValidProjectFileForCreation ( NewProjectFile , OutFailReason ) )
{
return false ;
}
const bool bAllowNewSlowTask = true ;
2014-06-09 15:41:37 -04:00
FScopedSlowTask SlowTaskMessage ( LOCTEXT ( " CreatingProjectStatus " , " Creating project... " ) , bAllowNewSlowTask ) ;
2014-03-14 14:13:41 -04:00
bool bProjectCreationSuccessful = false ;
FString TemplateName ;
if ( TemplateFile . IsEmpty ( ) )
{
bProjectCreationSuccessful = GenerateProjectFromScratch ( NewProjectFile , bShouldGenerateCode , bCopyStarterContent , OutFailReason ) ;
TemplateName = bShouldGenerateCode ? TEXT ( " Basic Code " ) : TEXT ( " Blank " ) ;
}
else
{
bProjectCreationSuccessful = CreateProjectFromTemplate ( NewProjectFile , TemplateFile , bShouldGenerateCode , bCopyStarterContent , OutFailReason ) ;
TemplateName = FPaths : : GetBaseFilename ( TemplateFile ) ;
}
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > EventAttributes ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " Template " ) , TemplateName ) ) ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " ProjectType " ) , bShouldGenerateCode ? TEXT ( " C++ Code " ) : TEXT ( " Content Only " ) ) ) ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " Outcome " ) , bProjectCreationSuccessful ? TEXT ( " Successful " ) : TEXT ( " Failed " ) ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Editor.NewProject.ProjectCreated " ) , EventAttributes ) ;
}
return bProjectCreationSuccessful ;
}
bool GameProjectUtils : : BuildGameBinaries ( const FString & ProjectFilename , FText & OutFailReason )
{
const bool bAllowNewSlowTask = true ;
2014-06-09 15:41:37 -04:00
FScopedSlowTask SlowTaskMessage ( LOCTEXT ( " BuildingProjectStatus " , " Building project... " ) , bAllowNewSlowTask ) ;
2014-03-14 14:13:41 -04:00
// Compile the *editor* for the project
if ( FModuleManager : : Get ( ) . CompileGameProjectEditor ( ProjectFilename , * GLog ) )
{
return true ;
}
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ProjectFilename " ) , FText : : FromString ( ProjectFilename ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToCompileNewProject " , " Failed to compile {ProjectFileName}. " ) , Args ) ;
return false ;
}
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 ( ) ;
}
}
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
{
2014-07-22 15:58:02 -04:00
return UpdateGameProjectFile ( ProjectFile , EngineIdentifier , NULL , OutFailReason ) ;
2014-03-14 14:13:41 -04:00
}
void GameProjectUtils : : OpenAddCodeToProjectDialog ( )
{
TSharedRef < SWindow > AddCodeWindow =
SNew ( SWindow )
. Title ( LOCTEXT ( " AddCodeWindowHeader " , " Add Code " ) )
2014-05-14 07:07:29 -04:00
. ClientSize ( FVector2D ( 940 , 540 ) )
2014-03-14 14:13:41 -04:00
. SizingRule ( ESizingRule : : FixedSize )
. SupportsMinimize ( false ) . SupportsMaximize ( false ) ;
AddCodeWindow - > SetContent ( SNew ( SNewClassDialog ) ) ;
IMainFrameModule & MainFrameModule = FModuleManager : : LoadModuleChecked < IMainFrameModule > ( TEXT ( " MainFrame " ) ) ;
if ( MainFrameModule . GetParentWindow ( ) . IsValid ( ) )
{
FSlateApplication : : Get ( ) . AddWindowAsNativeChild ( AddCodeWindow , MainFrameModule . GetParentWindow ( ) . ToSharedRef ( ) ) ;
}
else
{
FSlateApplication : : Get ( ) . AddWindow ( AddCodeWindow ) ;
}
}
2014-06-18 06:45:20 -04:00
bool GameProjectUtils : : IsValidClassNameForCreation ( const FString & NewClassName , const FModuleContextInfo & ModuleInfo , 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 ;
}
// 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 ;
}
}
return true ;
}
2014-08-04 18:21:05 -04:00
bool GameProjectUtils : : AddCodeToProject ( const FString & NewClassName , const FString & NewClassPath , const FModuleContextInfo & ModuleInfo , const FNewClassInfo ParentClassInfo , FString & OutHeaderFilePath , FString & OutCppFilePath , FText & OutFailReason )
2014-03-14 14:13:41 -04:00
{
2014-08-04 18:21:05 -04:00
const bool bAddCodeSuccessful = AddCodeToProject_Internal ( NewClassName , NewClassPath , ModuleInfo , ParentClassInfo , 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 ) ) ;
2014-03-14 14:13:41 -04:00
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " Outcome " ) , bAddCodeSuccessful ? TEXT ( " Successful " ) : TEXT ( " Failed " ) ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Editor.AddCodeToProject.CodeAdded " ) , EventAttributes ) ;
}
return bAddCodeSuccessful ;
}
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 ) ;
}
}
TemplateDefs = ConstructObject < UTemplateProjectDefs > ( ClassToConstruct ) ;
2014-03-14 14:13:41 -04:00
TemplateDefs - > LoadConfig ( UTemplateProjectDefs : : StaticClass ( ) , * TemplateDefsIniFilename ) ;
}
return TemplateDefs ;
}
bool GameProjectUtils : : GenerateProjectFromScratch ( const FString & NewProjectFile , bool bShouldGenerateCode , bool bCopyStarterContent , FText & OutFailReason )
{
const FString NewProjectFolder = FPaths : : GetPath ( NewProjectFile ) ;
const FString NewProjectName = FPaths : : GetBaseFilename ( NewProjectFile ) ;
TArray < FString > CreatedFiles ;
// Generate config files
if ( ! GenerateConfigFiles ( NewProjectFolder , NewProjectName , bShouldGenerateCode , bCopyStarterContent , CreatedFiles , OutFailReason ) )
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
// 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 ;
}
TArray < FString > StartupModuleNames ;
if ( bShouldGenerateCode )
{
// Generate basic source code files
if ( ! GenerateBasicSourceCode ( NewProjectFolder / TEXT ( " Source " ) , NewProjectName , StartupModuleNames , CreatedFiles , OutFailReason ) )
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
// Generate game framework source code files
if ( ! GenerateGameFrameworkSourceCode ( NewProjectFolder / TEXT ( " Source " ) , NewProjectName , CreatedFiles , OutFailReason ) )
{
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
}
// Generate the project file
{
FText LocalFailReason ;
2014-04-23 18:32:52 -04:00
if ( IProjectManager : : Get ( ) . GenerateNewProjectFile ( NewProjectFile , StartupModuleNames , FDesktopPlatformModule : : Get ( ) - > GetCurrentEngineIdentifier ( ) , LocalFailReason ) )
2014-03-14 14:13:41 -04:00
{
CreatedFiles . Add ( NewProjectFile ) ;
}
else
{
OutFailReason = LocalFailReason ;
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
}
if ( bShouldGenerateCode )
{
// Generate project files
if ( ! GenerateCodeProjectFiles ( NewProjectFile , OutFailReason ) )
{
DeleteGeneratedProjectFiles ( NewProjectFile ) ;
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
}
if ( bCopyStarterContent )
{
// Copy the starter content
if ( ! CopyStarterContent ( NewProjectFolder , OutFailReason ) )
{
DeleteGeneratedProjectFiles ( NewProjectFile ) ;
DeleteCreatedFiles ( NewProjectFolder , CreatedFiles ) ;
return false ;
}
}
UE_LOG ( LogGameProjectGeneration , Log , TEXT ( " Created new project with %d files (plus project files) " ) , CreatedFiles . Num ( ) ) ;
return true ;
}
bool GameProjectUtils : : CreateProjectFromTemplate ( const FString & NewProjectFile , const FString & TemplateFile , bool bShouldGenerateCode , bool bCopyStarterContent , FText & OutFailReason )
{
const FString ProjectName = FPaths : : GetBaseFilename ( NewProjectFile ) ;
const FString TemplateName = FPaths : : GetBaseFilename ( TemplateFile ) ;
const FString SrcFolder = FPaths : : GetPath ( TemplateFile ) ;
const FString DestFolder = FPaths : : GetPath ( NewProjectFile ) ;
if ( ! FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) . FileExists ( * TemplateFile ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " TemplateFile " ) , FText : : FromString ( TemplateFile ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " InvalidTemplate_MissingProject " , " Template project \" {TemplateFile} \" does not exist. " ) , Args ) ;
return false ;
}
UTemplateProjectDefs * TemplateDefs = LoadTemplateDefs ( SrcFolder ) ;
if ( TemplateDefs = = NULL )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " TemplateFile " ) , FText : : FromString ( FPaths : : GetBaseFilename ( TemplateFile ) ) ) ;
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 ;
}
// 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 ;
// 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 ) ;
for ( auto FileIt = FilesToCopy . CreateConstIterator ( ) ; FileIt ; + + FileIt )
{
const FString SrcFilename = ( * FileIt ) ;
// Get the file path, relative to the src folder
const FString SrcFileSubpath = SrcFilename . RightChop ( SrcFolder . Len ( ) + 1 ) ;
// Skip any files that were configured to be ignored
bool bThisFileIsIgnored = false ;
for ( auto IgnoreIt = TemplateDefs - > FilesToIgnore . CreateConstIterator ( ) ; IgnoreIt ; + + IgnoreIt )
{
if ( SrcFileSubpath = = * IgnoreIt )
{
// This file was marked as "ignored"
bThisFileIsIgnored = true ;
break ;
}
}
if ( bThisFileIsIgnored )
{
// 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"
bThisFolderIsIgnored = true ;
break ;
}
}
if ( bThisFolderIsIgnored )
{
// This folder was marked as "ignored"
continue ;
}
// Update the slow task dialog
const bool bAllowNewSlowTask = false ;
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " SrcFilename " ) , FText : : FromString ( FPaths : : GetCleanFilename ( SrcFilename ) ) ) ;
2014-06-09 15:41:37 -04:00
FScopedSlowTask SlowTaskMessage ( FText : : Format ( LOCTEXT ( " CreatingProjectStatus_CopyingFile " , " Copying File {SrcFilename}... " ) , Args ) , bAllowNewSlowTask ) ;
2014-03-14 14:13:41 -04:00
// 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 ( ) ) ;
}
}
// 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
DestBaseFilename = DestBaseFilename . Replace ( * Replacement . From , * Replacement . To , Replacement . bCaseSensitive ? ESearchCase : : CaseSensitive : ESearchCase : : IgnoreCase ) ;
}
}
// 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 ) ;
}
2014-08-11 17:14:26 -04:00
// Allow project template to extract class renames from this file copy
if ( FPaths : : GetBaseFilename ( SrcFilename ) ! = FPaths : : GetBaseFilename ( DestFilename )
& & TemplateDefs - > IsClassRename ( DestFilename , SrcFilename , FileExtension ) )
2014-03-14 14:13:41 -04:00
{
2014-08-11 17:14:26 -04:00
// Looks like a UObject file!
ClassRenames . Add ( FPaths : : GetBaseFilename ( SrcFilename ) , FPaths : : GetBaseFilename ( DestFilename ) ) ;
2014-03-14 14:13:41 -04:00
}
}
else
{
2014-03-15 01:14:25 -04:00
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 ) ;
2014-03-14 14:13:41 -04:00
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
}
// Open all files with the specified extensions and replace text
for ( auto FileIt = FilesThatNeedContentsReplaced . CreateConstIterator ( ) ; FileIt ; + + FileIt )
{
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 ;
}
}
// Fixup specific ini values
2014-08-11 17:14:26 -04:00
TArray < FTemplateConfigValue > ConfigValuesToSet ;
TemplateDefs - > AddConfigValues ( ConfigValuesToSet , TemplateName , ProjectName , bShouldGenerateCode ) ;
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-03-14 14:13:41 -04:00
const FString IniFilename = DestFolder / TEXT ( " Config " ) / ConfigValue . ConfigFile ;
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 ;
}
}
// Generate the project file
{
2014-07-22 15:58:02 -04:00
// Load the source project
FProjectDescriptor Project ;
if ( ! Project . Load ( 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
Project . EngineAssociation = FDesktopPlatformModule : : Get ( ) - > GetCurrentEngineIdentifier ( ) ;
Project . EpicSampleNameHash = 0 ;
// Fix up module names
const FString BaseSourceName = FPaths : : GetBaseFilename ( TemplateFile ) ;
const FString BaseNewName = FPaths : : GetBaseFilename ( NewProjectFile ) ;
for ( auto ModuleIt = Project . Modules . CreateIterator ( ) ; ModuleIt ; + + ModuleIt )
{
FModuleDescriptor & ModuleInfo = * ModuleIt ;
ModuleInfo . Name = FName ( * ModuleInfo . Name . ToString ( ) . Replace ( * BaseSourceName , * BaseNewName ) ) ;
}
// Save it to disk
if ( ! Project . Save ( NewProjectFile , OutFailReason ) )
{
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
// Add it to the list of created files
CreatedFiles . Add ( NewProjectFile ) ;
2014-03-14 14:13:41 -04:00
}
if ( bShouldGenerateCode )
{
// resource folder
const FString GameModuleSourcePath = DestFolder / TEXT ( " Source " ) / ProjectName ;
if ( GenerateGameResourceFiles ( GameModuleSourcePath , ProjectName , CreatedFiles , OutFailReason ) = = false )
{
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
// Generate project files
if ( ! GenerateCodeProjectFiles ( NewProjectFile , OutFailReason ) )
{
DeleteGeneratedProjectFiles ( NewProjectFile ) ;
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
}
if ( bCopyStarterContent )
{
// Copy the starter content
if ( ! CopyStarterContent ( DestFolder , OutFailReason ) )
{
DeleteGeneratedProjectFiles ( NewProjectFile ) ;
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
}
2014-08-11 17:14:26 -04:00
if ( ! TemplateDefs - > PostGenerateProject ( DestFolder , SrcFolder , NewProjectFile , TemplateFile , bShouldGenerateCode , OutFailReason ) )
{
DeleteGeneratedProjectFiles ( NewProjectFile ) ;
DeleteCreatedFiles ( DestFolder , CreatedFiles ) ;
return false ;
}
2014-03-14 14:13:41 -04:00
return true ;
}
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 )
{
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 ;
const FString Wildcard = FString : : Printf ( TEXT ( " %s/*.%s " ) , * Path , * IProjectManager : : GetProjectFileExtension ( ) ) ;
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 ) ;
}
}
bool GameProjectUtils : : GenerateConfigFiles ( const FString & NewProjectPath , const FString & NewProjectName , bool bShouldGenerateCode , bool bCopyStarterContent , TArray < FString > & OutCreatedFiles , FText & OutFailReason )
{
FString ProjectConfigPath = NewProjectPath / TEXT ( " Config " ) ;
// DefaultEngine.ini
{
const FString DefaultEngineIniFilename = ProjectConfigPath / TEXT ( " DefaultEngine.ini " ) ;
FString FileContents ;
FileContents + = TEXT ( " [URL] " ) LINE_TERMINATOR ;
FileContents + = FString : : Printf ( TEXT ( " GameName=%s " ) LINE_TERMINATOR , * NewProjectName ) ;
FileContents + = LINE_TERMINATOR ;
if ( bCopyStarterContent )
{
// for generated/blank projects with starter content, set startup map to be the starter content map
// otherwise, we leave it to be what the template wants.
TArray < FString > StarterContentMapFiles ;
const FString FileWildcard = FString ( TEXT ( " * " ) ) + FPackageName : : GetMapPackageExtension ( ) ;
// assume the first map in the /Maps folder is the default map
IFileManager : : Get ( ) . FindFilesRecursive ( StarterContentMapFiles , * FPaths : : StarterContentDir ( ) , * FileWildcard , /*Files=*/ true , /*Directories=*/ false ) ;
if ( StarterContentMapFiles . Num ( ) > 0 )
{
FString StarterContentContentDir = FPaths : : StarterContentDir ( ) + TEXT ( " Content/ " ) ;
const FString BaseMapFilename = FPaths : : GetBaseFilename ( StarterContentMapFiles [ 0 ] ) ;
FString MapPathRelToContent = FPaths : : GetPath ( StarterContentMapFiles [ 0 ] ) ;
FPaths : : MakePathRelativeTo ( MapPathRelToContent , * StarterContentContentDir ) ;
const FString MapPackagePath = FString ( TEXT ( " /Game/ " ) ) + MapPathRelToContent + TEXT ( " / " ) + BaseMapFilename ;
FileContents + = TEXT ( " [/Script/EngineSettings.GameMapsSettings] " ) LINE_TERMINATOR ;
FileContents + = FString : : Printf ( TEXT ( " EditorStartupMap=%s " ) LINE_TERMINATOR , * MapPackagePath ) ;
FileContents + = FString : : Printf ( TEXT ( " GameDefaultMap=%s " ) LINE_TERMINATOR , * MapPackagePath ) ;
2014-06-06 14:15:04 -04:00
if ( 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 ;
}
}
// DefaultGame.ini
{
const FString DefaultGameIniFilename = ProjectConfigPath / TEXT ( " DefaultGame.ini " ) ;
FString FileContents ;
FileContents + = TEXT ( " [/Script/EngineSettings.GeneralProjectSettings] " ) LINE_TERMINATOR ;
FileContents + = FString : : Printf ( TEXT ( " ProjectID=%s " ) LINE_TERMINATOR , * FGuid : : NewGuid ( ) . ToString ( ) ) ;
FileContents + = LINE_TERMINATOR ;
if ( WriteOutputFile ( DefaultGameIniFilename , FileContents , OutFailReason ) )
{
OutCreatedFiles . Add ( DefaultGameIniFilename ) ;
}
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 ;
}
}
2014-03-14 14:13:41 -04:00
return true ;
}
bool GameProjectUtils : : GenerateBasicSourceCode ( const FString & NewProjectSourcePath , const FString & NewProjectName , TArray < FString > & OutGeneratedStartupModuleNames , TArray < FString > & OutCreatedFiles , FText & OutFailReason )
{
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 resource folder
if ( GenerateGameResourceFiles ( GameModulePath , NewProjectName , OutCreatedFiles , OutFailReason ) = = false )
{
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
// MyGamePlayerController.h
{
const UClass * BaseClass = APlayerController : : 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-05-16 10:47:37 -04:00
if ( GenerateClassHeaderFile ( NewHeaderFilename , NewClassName , FNewClassInfo ( BaseClass ) , TArray < FString > ( ) , TEXT ( " " ) , TEXT ( " " ) , UnusedSyncLocation , NewModuleInfo , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
OutCreatedFiles . Add ( NewHeaderFilename ) ;
}
else
{
return false ;
}
}
// 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-05-16 10:47:37 -04:00
if ( GenerateClassHeaderFile ( NewHeaderFilename , NewClassName , FNewClassInfo ( BaseClass ) , TArray < FString > ( ) , TEXT ( " " ) , TEXT ( " " ) , UnusedSyncLocation , NewModuleInfo , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
OutCreatedFiles . Add ( NewHeaderFilename ) ;
}
else
{
return false ;
}
}
// MyGamePlayerController.cpp
FString PrefixedPlayerControllerClassName ;
{
const UClass * BaseClass = APlayerController : : StaticClass ( ) ;
2014-05-16 07:26:51 -04:00
const FString NewClassName = NewProjectName + BaseClass - > GetName ( ) ;
const FString NewCPPFilename = GameModulePath / NewClassName + TEXT ( " .cpp " ) ;
PrefixedPlayerControllerClassName = FString ( BaseClass - > GetPrefixCPP ( ) ) + NewClassName ;
2014-05-16 10:47:37 -04:00
if ( GenerateClassCPPFile ( NewCPPFilename , NewClassName , FNewClassInfo ( BaseClass ) , TArray < FString > ( ) , TArray < FString > ( ) , TEXT ( " " ) , NewModuleInfo , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
OutCreatedFiles . Add ( NewCPPFilename ) ;
}
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 ;
PropertyOverrides . Add ( FString : : Printf ( TEXT ( " PlayerControllerClass = %s::StaticClass(); " ) , * PrefixedPlayerControllerClassName ) ) ;
// PropertyOverrides references PlayerController class so we need to include its header to properly compile under non-unity
const UClass * PlayerControllerBaseClass = APlayerController : : StaticClass ( ) ;
const FString PlayerControllerClassName = NewProjectName + PlayerControllerBaseClass - > GetName ( ) + TEXT ( " .h " ) ;
TArray < FString > AdditionalIncludes ;
AdditionalIncludes . Add ( PlayerControllerClassName ) ;
2014-05-16 10:47:37 -04:00
if ( GenerateClassCPPFile ( NewCPPFilename , NewClassName , FNewClassInfo ( BaseClass ) , AdditionalIncludes , PropertyOverrides , TEXT ( " " ) , NewModuleInfo , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
OutCreatedFiles . Add ( NewCPPFilename ) ;
}
else
{
return false ;
}
}
return true ;
}
bool GameProjectUtils : : GenerateCodeProjectFiles ( const FString & ProjectFilename , FText & OutFailReason )
{
if ( FModuleManager : : Get ( ) . GenerateCodeProjectFiles ( ProjectFilename , * GLog ) )
{
return true ;
}
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " ProjectFilename " ) , FText : : FromString ( ProjectFilename ) ) ;
OutFailReason = FText : : Format ( LOCTEXT ( " FailedToGenerateCodeProjectFiles " , " Failed to generate code project files for \" {ProjectFilename} \" . " ) , Args ) ;
return false ;
}
bool GameProjectUtils : : IsStarterContentAvailableForNewProjects ( )
{
TArray < FString > StarterContentFiles ;
GetStarterContentFiles ( StarterContentFiles ) ;
return ( StarterContentFiles . Num ( ) > 0 ) ;
}
2014-08-04 18:21:05 -04:00
TArray < GameProjectUtils : : 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-05-13 18:23:53 -04:00
bool GameProjectUtils : : DuplicateProjectForUpgrade ( const FString & InProjectFile , FString & OutNewProjectFile )
{
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 ;
GWarn - > BeginSlowTask ( LOCTEXT ( " CreatingCopyOfProject " , " Creating copy of project... " ) , true ) ;
for ( int32 Idx = 0 ; Idx < SourceDirectories . Num ( ) & & bCopySucceeded ; Idx + + )
{
FString TargetDirectory = NewDirectoryName + SourceDirectories [ Idx ] . Mid ( OldDirectoryName . Len ( ) ) ;
bCopySucceeded = PlatformFile . CreateDirectory ( * TargetDirectory ) ;
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 ( ) ) ;
bCopySucceeded = PlatformFile . CopyFile ( * TargetFile , * SourceFiles [ Idx ] ) ;
GWarn - > UpdateProgress ( SourceDirectories . Num ( ) + Idx + 1 , SourceDirectories . Num ( ) + SourceFiles . Num ( ) ) ;
}
GWarn - > EndSlowTask ( ) ;
// Wipe the directory if we couldn't update
if ( ! bCopySucceeded )
{
PlatformFile . DeleteDirectoryRecursively ( * NewDirectoryName ) ;
return false ;
}
// Otherwise fixup the output project filename
OutNewProjectFile = NewDirectoryName / FPaths : : GetCleanFilename ( InProjectFile ) ;
return true ;
}
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-04-02 18:09:23 -04:00
if ( FEngineBuildSettings : : IsInternalBuild ( ) )
{
return FString ( TEXT ( " // " ) ) + Cast < UGeneralProjectSettings > ( UGeneralProjectSettings : : StaticClass ( ) - > GetDefaultObject ( ) ) - > CopyrightNotice ;
}
return " " ;
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-05-16 10:47:37 -04:00
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 , 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-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 ) ;
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 ) ;
// Determine the cursor focus location if this file will by synced after creation
TArray < FString > Lines ;
FinalOutput . ParseIntoArray ( & Lines , TEXT ( " \n " ) , false ) ;
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 ) ;
return WriteOutputFile ( NewHeaderFileName , FinalOutput , OutFailReason ) ;
}
2014-05-16 10:47:37 -04:00
bool GameProjectUtils : : GenerateClassCPPFile ( const FString & NewCPPFileName , const FString UnPrefixedClassName , const FNewClassInfo ParentClassInfo , const TArray < FString > & AdditionalIncludes , const TArray < FString > & PropertyOverrides , const FString & AdditionalMemberDefinitions , 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
FString ModuleIncludePath ;
2014-08-04 18:21:05 -04:00
if ( FindSourceFileInProject ( ModuleInfo . ModuleName + " .h " , ModuleInfo . ModuleSourcePath , ModuleIncludePath ) )
2014-06-18 06:45:20 -04:00
{
// 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 ;
2014-08-04 18:21:05 -04:00
if ( GetClassLocation ( ModuleIncludePath , ModuleInfo , ModuleLocation ) )
2014-06-18 06:45:20 -04:00
{
if ( ModuleLocation = = EClassLocation : : Public | | ModuleLocation = = EClassLocation : : Classes )
{
2014-08-04 18:21:05 -04:00
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
2014-06-18 06:45:20 -04:00
}
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 ( NewCPPFileName ) ) ;
if ( ModulePath = = ClassPath )
{
2014-08-04 18:21:05 -04:00
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
2014-06-18 06:45:20 -04:00
}
else
{
// Updates ModuleIncludePath internally
2014-08-04 18:21:05 -04:00
if ( ! FPaths : : MakePathRelativeTo ( ModuleIncludePath , * ModuleInfo . ModuleSourcePath ) )
2014-06-18 06:45:20 -04:00
{
// Failed; just assume we can include it without any relative path
2014-08-04 18:21:05 -04:00
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
2014-06-18 06:45:20 -04:00
}
}
}
}
else
{
// Failed; just assume we can include it without any relative path
2014-08-04 18:21:05 -04:00
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
2014-06-18 06:45:20 -04:00
}
}
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
2014-08-04 18:21:05 -04:00
ModuleIncludePath = ModuleInfo . ModuleName + " .h " ;
2014-06-18 06:45:20 -04:00
}
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 ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %PROPERTY_OVERRIDES% " ) , * PropertyOverridesStr , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %ADDITIONAL_MEMBER_DEFINITIONS% " ) , * AdditionalMemberDefinitions , ESearchCase : : CaseSensitive ) ;
FinalOutput = FinalOutput . Replace ( TEXT ( " %ADDITIONAL_INCLUDE_DIRECTIVES% " ) , * AdditionalIncludesStr , ESearchCase : : CaseSensitive ) ;
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 : : GenerateGameResourceFile ( const FString & NewResourceFolderName , const FString & TemplateFilename , const FString & GameName , TArray < FString > & OutCreatedFiles , FText & OutFailReason )
{
FString Template ;
if ( ! ReadTemplateFile ( TemplateFilename , Template , OutFailReason ) )
{
return false ;
}
FString FinalOutput = Template . Replace ( TEXT ( " %GAME_NAME% " ) , * GameName , ESearchCase : : CaseSensitive ) ;
FString OutputFilename = TemplateFilename . Replace ( TEXT ( " _GAME_NAME_ " ) , * GameName ) ;
FString FullOutputFilename = NewResourceFolderName / OutputFilename ;
struct Local
{
2014-03-15 01:14:25 -04:00
static bool WriteFile ( const FString & InDestFile , const FText & InFileDescription , FText & OutFailureReason , FString * InFileContents , TArray < FString > * OutCreatedFileList )
2014-03-14 14:13:41 -04:00
{
2014-03-15 01:14:25 -04:00
if ( WriteOutputFile ( InDestFile , * InFileContents , OutFailureReason ) )
2014-03-14 14:13:41 -04:00
{
2014-03-15 01:14:25 -04:00
OutCreatedFileList - > Add ( InDestFile ) ;
2014-03-14 14:13:41 -04:00
return true ;
}
return false ;
}
} ;
return SourceControlHelpers : : CheckoutOrMarkForAdd ( FullOutputFilename , LOCTEXT ( " ResourceFileDescription " , " resource " ) , FOnPostCheckOut : : CreateStatic ( & Local : : WriteFile , & FinalOutput , & OutCreatedFiles ) , OutFailReason ) ;
}
bool GameProjectUtils : : GenerateGameResourceFiles ( const FString & NewResourceFolderName , const FString & GameName , TArray < FString > & OutCreatedFiles , FText & OutFailReason )
{
bool bSucceeded = true ;
FString TemplateFilename ;
# if PLATFORM_WINDOWS
FString IconPartialName = TEXT ( " _GAME_NAME_ " ) ;
// Icon (just copy this)
TemplateFilename = FString : : Printf ( TEXT ( " Resources/Windows/%s.ico " ) , * IconPartialName ) ;
FString FullTemplateFilename = FPaths : : EngineContentDir ( ) / TEXT ( " Editor " ) / TEXT ( " Templates " ) / TemplateFilename ;
FString OutputFilename = TemplateFilename . Replace ( * IconPartialName , * GameName ) ;
FString FullOutputFilename = NewResourceFolderName / OutputFilename ;
bSucceeded & = SourceControlHelpers : : CopyFileUnderSourceControl ( FullOutputFilename , FullTemplateFilename , LOCTEXT ( " IconFileDescription " , " icon " ) , OutFailReason ) ;
if ( bSucceeded )
{
OutCreatedFiles . Add ( FullOutputFilename ) ;
}
// RC
TemplateFilename = TEXT ( " Resources/Windows/_GAME_NAME_.rc " ) ;
bSucceeded & = GenerateGameResourceFile ( NewResourceFolderName , TemplateFilename , GameName , OutCreatedFiles , OutFailReason ) ;
# elif PLATFORM_MAC
//@todo MAC: Implement MAC version of these files...
# endif
return bSucceeded ;
}
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 ( )
{
UpdateProject ( NULL ) ;
}
void GameProjectUtils : : UpdateProject ( const TArray < FString > * StartupModuleNames )
{
const FString & ProjectFilename = FPaths : : GetProjectFilePath ( ) ;
const FString & ShortFilename = FPaths : : GetCleanFilename ( ProjectFilename ) ;
FText FailReason ;
FText UpdateMessage ;
SNotificationItem : : ECompletionState NewCompletionState ;
2014-07-22 15:58:02 -04:00
if ( UpdateGameProjectFile ( ProjectFilename , FDesktopPlatformModule : : Get ( ) - > GetCurrentEngineIdentifier ( ) , StartupModuleNames , 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 ( ) ;
}
}
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
}
2014-07-22 15:58:02 -04:00
bool GameProjectUtils : : UpdateGameProjectFile ( const FString & ProjectFile , const FString & EngineIdentifier , const TArray < FString > * StartupModuleNames , 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
{
2014-07-22 15:58:02 -04:00
// Freshen version information
Descriptor . EngineAssociation = EngineIdentifier ;
2014-03-14 14:13:41 -04:00
2014-07-22 15:58:02 -04:00
// Replace the modules names, if specified
if ( StartupModuleNames ! = NULL )
{
Descriptor . Modules . Empty ( ) ;
for ( int32 Idx = 0 ; Idx < StartupModuleNames - > Num ( ) ; Idx + + )
{
Descriptor . Modules . Add ( FModuleDescriptor ( * ( * StartupModuleNames ) [ Idx ] ) ) ;
}
}
// Update file on disk
return Descriptor . Save ( ProjectFile , OutFailReason ) ;
}
return false ;
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 ( " " ) ;
}
int32 GameProjectUtils : : GetProjectCodeFileCount ( )
{
TArray < FString > Filenames ;
IFileManager : : Get ( ) . FindFilesRecursive ( Filenames , * FPaths : : GameSourceDir ( ) , TEXT ( " *.h " ) , true , false , false ) ;
IFileManager : : Get ( ) . FindFilesRecursive ( Filenames , * FPaths : : GameSourceDir ( ) , TEXT ( " *.cpp " ) , true , false , false ) ;
return Filenames . Num ( ) ;
}
bool GameProjectUtils : : ProjectHasCodeFiles ( )
{
return GameProjectUtils : : GetProjectCodeFileCount ( ) > 0 ;
}
2014-08-04 18:21:05 -04:00
bool GameProjectUtils : : AddCodeToProject_Internal ( const FString & NewClassName , const FString & NewClassPath , const FModuleContextInfo & ModuleInfo , const FNewClassInfo ParentClassInfo , 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 " ) ;
return false ;
}
2014-05-16 07:26:51 -04:00
const FString CleanClassName = ParentClassInfo . GetCleanClassName ( NewClassName ) ;
const FString FinalClassName = ParentClassInfo . GetFinalClassName ( NewClassName ) ;
2014-06-18 06:45:20 -04:00
if ( ! IsValidClassNameForCreation ( FinalClassName , ModuleInfo , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
return false ;
}
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. " ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
#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
{
return false ;
}
2014-03-14 14:13:41 -04:00
const bool bAllowNewSlowTask = true ;
2014-06-09 15:41:37 -04:00
FScopedSlowTask SlowTaskMessage ( LOCTEXT ( " AddingCodeToProject " , " Adding code to project... " ) , bAllowNewSlowTask ) ;
2014-03-14 14:13:41 -04:00
// If the project does not already contain code, add the primary game module
TArray < FString > CreatedFiles ;
if ( ! ProjectHasCodeFiles ( ) )
{
#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-03-14 14:13:41 -04:00
TArray < FString > StartupModuleNames ;
2014-05-16 07:26:51 -04:00
if ( GenerateBasicSourceCode ( SourceDir , GameModuleName , StartupModuleNames , CreatedFiles , OutFailReason ) )
2014-03-14 14:13:41 -04:00
{
UpdateProject ( & StartupModuleNames ) ;
}
else
{
DeleteCreatedFiles ( SourceDir , CreatedFiles ) ;
return false ;
}
}
// Class Header File
FString SyncLocation ;
2014-05-16 07:26:51 -04:00
const FString NewHeaderFilename = NewHeaderPath / ParentClassInfo . GetHeaderFilename ( NewClassName ) ;
2014-03-14 14:13:41 -04:00
{
2014-05-16 10:47:37 -04:00
if ( GenerateClassHeaderFile ( NewHeaderFilename , CleanClassName , ParentClassInfo , TArray < FString > ( ) , TEXT ( " " ) , TEXT ( " " ) , SyncLocation , ModuleInfo , 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 ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
}
// 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
{
2014-05-16 10:47:37 -04:00
if ( GenerateClassCPPFile ( NewCppFilename , CleanClassName , ParentClassInfo , TArray < FString > ( ) , TArray < FString > ( ) , TEXT ( " " ) , 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 ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
}
// Generate project files if we happen to be using a project file.
if ( ! FModuleManager : : Get ( ) . GenerateCodeProjectFiles ( FPaths : : GetProjectFilePath ( ) , * GLog ) )
{
OutFailReason = LOCTEXT ( " FailedToGenerateProjectFiles " , " Failed to generate project files. " ) ;
return false ;
}
// Mark the files for add in SCC
ISourceControlProvider & SourceControlProvider = ISourceControlModule : : Get ( ) . GetProvider ( ) ;
if ( ISourceControlModule : : Get ( ) . IsEnabled ( ) & & SourceControlProvider . IsAvailable ( ) )
{
TArray < FString > FilesToCheckOut ;
for ( auto FileIt = CreatedFiles . CreateConstIterator ( ) ; FileIt ; + + FileIt )
{
FilesToCheckOut . Add ( IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForRead ( * * FileIt ) ) ;
}
SourceControlProvider . Execute ( ISourceControlOperation : : Create < FMarkForAdd > ( ) , FilesToCheckOut ) ;
}
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
return true ;
}
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 ;
const FString FilenameWidcard = TEXT ( " * " ) + InFilename ;
2014-08-04 18:21:05 -04:00
IFileManager : : Get ( ) . FindFilesRecursive ( Filenames , * InSearchPath , * FilenameWidcard , 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
}
2014-03-14 14:13:41 -04:00
# undef LOCTEXT_NAMESPACE