2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Xml ;
using System.Xml.XPath ;
using System.Xml.Linq ;
using System.Linq ;
using System.Text ;
namespace UnrealBuildTool
{
/// <summary>
/// Info needed to make a file a member of specific group
/// </summary>
class XcodeSourceFile : ProjectFile . SourceFile
{
/// <summary>
/// Constructor
/// </summary>
public XcodeSourceFile ( string InitFilePath , string InitRelativeBaseFolder )
: base ( InitFilePath , InitRelativeBaseFolder )
{
FileGUID = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
FileRefGUID = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
}
/// <summary>
/// File GUID for use in Xcode project
/// </summary>
public string FileGUID
{
get ;
private set ;
}
public void ReplaceGUIDS ( string NewFileGUID , string NewFileRefGUID )
{
FileGUID = NewFileGUID ;
FileRefGUID = NewFileRefGUID ;
}
/// <summary>
/// File reference GUID for use in Xcode project
/// </summary>
public string FileRefGUID
{
get ;
private set ;
}
}
/// <summary>
/// Represents a group of files shown in Xcode's project navigator as a folder
/// </summary>
class XcodeFileGroup
{
/// <summary>
/// Constructor
/// </summary>
2014-04-30 09:53:25 -04:00
public XcodeFileGroup ( string InName , string InPath , bool InReference = false )
2014-03-14 14:13:41 -04:00
{
GroupName = InName ;
GroupPath = InPath ;
GroupGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
2014-04-30 09:53:25 -04:00
bReference = InReference ;
2014-03-14 14:13:41 -04:00
}
/// <summary>
/// Appends a group entry (and, recusrively, it's children) to Contents. Should be called while generating PBXGroup section.
/// </summary>
2014-04-02 18:09:23 -04:00
public void Append ( ref StringBuilder Contents , bool bFilesOnly = false )
2014-03-14 14:13:41 -04:00
{
2014-04-30 09:53:25 -04:00
if ( bReference )
{
return ;
}
2014-03-14 14:13:41 -04:00
2014-04-02 18:09:23 -04:00
if ( ! bFilesOnly )
2014-03-14 14:13:41 -04:00
{
2014-04-02 18:09:23 -04:00
Contents . Append ( string . Format ( "\t\t{0} = {{{1}" , GroupGuid , ProjectFileGenerator . NewLine ) ) ;
Contents . Append ( "\t\t\tisa = PBXGroup;" + ProjectFileGenerator . NewLine ) ;
Contents . Append ( "\t\t\tchildren = (" + ProjectFileGenerator . NewLine ) ;
foreach ( XcodeFileGroup Group in Children . Values )
{
Contents . Append ( string . Format ( "\t\t\t\t{0} /* {1} */,{2}" , Group . GroupGuid , Group . GroupName , ProjectFileGenerator . NewLine ) ) ;
}
2014-03-14 14:13:41 -04:00
}
2014-04-02 18:09:23 -04:00
2014-03-14 14:13:41 -04:00
foreach ( XcodeSourceFile File in Files )
{
Contents . Append ( string . Format ( "\t\t\t\t{0} /* {1} */,{2}" , File . FileRefGUID , Path . GetFileName ( File . FilePath ) , ProjectFileGenerator . NewLine ) ) ;
}
2014-04-02 18:09:23 -04:00
if ( ! bFilesOnly )
2014-03-14 14:13:41 -04:00
{
2014-04-02 18:09:23 -04:00
Contents . Append ( "\t\t\t);" + ProjectFileGenerator . NewLine ) ;
Contents . Append ( "\t\t\tname = \"" + GroupName + "\";" + ProjectFileGenerator . NewLine ) ;
Contents . Append ( "\t\t\tpath = \"" + GroupPath + "\";" + ProjectFileGenerator . NewLine ) ;
Contents . Append ( "\t\t\tsourceTree = \"<group>\";" + ProjectFileGenerator . NewLine ) ;
Contents . Append ( "\t\t};" + ProjectFileGenerator . NewLine ) ;
foreach ( XcodeFileGroup Group in Children . Values )
{
Group . Append ( ref Contents ) ;
}
2014-03-14 14:13:41 -04:00
}
}
public string GroupGuid ;
public string GroupName ;
public string GroupPath ;
public Dictionary < string , XcodeFileGroup > Children = new Dictionary < string , XcodeFileGroup > ( ) ;
public List < XcodeSourceFile > Files = new List < XcodeSourceFile > ( ) ;
2014-04-30 09:53:25 -04:00
public bool bReference ;
2014-03-14 14:13:41 -04:00
}
class XcodeProjectFile : ProjectFile
{
public string UE4CmdLineRunFileGuid = null ;
public string UE4CmdLineRunFileRefGuid = null ;
/// <summary>
/// Constructs a new project file object
/// </summary>
/// <param name="InitFilePath">The path to the project file on disk</param>
public XcodeProjectFile ( string InitFilePath )
: base ( InitFilePath )
{
}
/** Gets Xcode file category based on its extension */
private string GetFileCategory ( string Extension )
{
// @todo Mac: Handle more categories
switch ( Extension )
{
case ".framework" :
return "Frameworks" ;
default :
return "Sources" ;
}
}
/** Gets Xcode file type based on its extension */
private string GetFileType ( string Extension )
{
// @todo Mac: Handle more file types
switch ( Extension )
{
case ".c" :
case ".m" :
return "sourcecode.c.objc" ;
2014-04-23 17:32:01 -04:00
case ".cc" :
case ".cpp" :
2014-03-14 14:13:41 -04:00
case ".mm" :
return "sourcecode.cpp.objcpp" ;
case ".h" :
2014-04-23 17:32:01 -04:00
case ".inl" :
2014-03-14 14:13:41 -04:00
case ".pch" :
return "sourcecode.c.h" ;
case ".framework" :
return "wrapper.framework" ;
default :
return "file.text" ;
}
}
/** Returns true if Extension is a known extension for files containing source code */
private bool IsSourceCode ( string Extension )
{
return Extension = = ".c" | | Extension = = ".cc" | | Extension = = ".cpp" | | Extension = = ".m" | | Extension = = ".mm" ;
}
2014-04-23 17:32:01 -04:00
private bool IsPartOfUE4XcodeHelperTarget ( XcodeSourceFile SourceFile )
{
string FileExtension = Path . GetExtension ( SourceFile . FilePath ) ;
if ( IsSourceCode ( FileExtension ) ) // || GetFileType(FileExtension) == "sourcecode.c.h") @todo: It seemed that headers need to be added to project for live issues detection to work in them
{
foreach ( string PlatformName in Enum . GetNames ( typeof ( UnrealTargetPlatform ) ) )
{
string AltName = PlatformName = = "Win32" | | PlatformName = = "Win64" ? "windows" : PlatformName . ToLower ( ) ;
2014-04-24 13:23:48 -04:00
if ( ( SourceFile . FilePath . ToLower ( ) . Contains ( "/" + PlatformName . ToLower ( ) + "/" ) | | SourceFile . FilePath . ToLower ( ) . Contains ( "/" + AltName + "/" ) ) & & PlatformName ! = "Mac" )
2014-04-23 17:32:01 -04:00
{
// UE4XcodeHelper is Mac only target, so skip other platforms files
return false ;
}
2014-06-18 12:16:38 -04:00
else if ( SourceFile . FilePath . EndsWith ( "SimplygonMeshReduction.cpp" ) | | SourceFile . FilePath . EndsWith ( "MeshBoneReduction.cpp" ) | | SourceFile . FilePath . EndsWith ( "Android.cpp" )
| | SourceFile . FilePath . EndsWith ( "Amazon.cpp" ) | | SourceFile . FilePath . EndsWith ( "FacebookModule.cpp" ) | | SourceFile . FilePath . EndsWith ( "SDL_angle.c" )
| | SourceFile . FilePath . Contains ( "VisualStudioSourceCodeAccess" ) | | SourceFile . FilePath . Contains ( "AndroidDevice" ) | | SourceFile . FilePath . Contains ( "IOSDevice" )
| | SourceFile . FilePath . Contains ( "WindowsDevice" ) | | SourceFile . FilePath . Contains ( "WindowsMoviePlayer" ) | | SourceFile . FilePath . EndsWith ( "IOSTapJoy.cpp" ) )
2014-04-23 17:32:01 -04:00
{
// @todo: We need a way to filter out files that use SDKs we don't have
return false ;
}
}
return true ;
}
return false ;
}
2014-03-14 14:13:41 -04:00
/ * *
* Returns a project navigator group to which the file should belong based on its path .
* Creates a group tree if it doesn ' t exist yet .
* /
public XcodeFileGroup FindGroupByFullPath ( ref Dictionary < string , XcodeFileGroup > Groups , string FullPath )
{
2014-04-02 18:09:23 -04:00
string RelativePath = ( FullPath = = XcodeProjectFileGenerator . MasterProjectRelativePath ) ? "" : Utils . MakePathRelativeTo ( FullPath , XcodeProjectFileGenerator . MasterProjectRelativePath ) ;
2014-03-14 14:13:41 -04:00
if ( RelativePath . StartsWith ( ".." ) )
{
string UE4Dir = Path . GetFullPath ( Directory . GetCurrentDirectory ( ) + "../../.." ) ;
RelativePath = Utils . MakePathRelativeTo ( FullPath , UE4Dir ) ;
}
string [ ] Parts = RelativePath . Split ( Path . DirectorySeparatorChar ) ;
string CurrentPath = "" ;
Dictionary < string , XcodeFileGroup > CurrentParent = Groups ;
foreach ( string Part in Parts )
{
XcodeFileGroup CurrentGroup ;
if ( CurrentPath ! = "" )
{
CurrentPath + = Path . DirectorySeparatorChar ;
}
CurrentPath + = Part ;
if ( ! CurrentParent . ContainsKey ( CurrentPath ) )
{
CurrentGroup = new XcodeFileGroup ( Path . GetFileName ( CurrentPath ) , CurrentPath ) ;
CurrentParent . Add ( CurrentPath , CurrentGroup ) ;
}
else
{
CurrentGroup = CurrentParent [ CurrentPath ] ;
}
if ( CurrentPath = = RelativePath )
{
return CurrentGroup ;
}
CurrentParent = CurrentGroup . Children ;
}
return null ;
}
/// <summary>
/// Allocates a generator-specific source file object
/// </summary>
/// <param name="InitFilePath">Path to the source file on disk</param>
/// <param name="InitProjectSubFolder">Optional sub-folder to put the file in. If empty, this will be determined automatically from the file's path relative to the project file</param>
/// <returns>The newly allocated source file object</returns>
2014-04-23 17:32:01 -04:00
public override SourceFile AllocSourceFile ( string InitFilePath , string InitProjectSubFolder )
2014-03-14 14:13:41 -04:00
{
if ( Path . GetFileName ( InitFilePath ) = = ".DS_Store" )
{
return null ;
2014-04-23 17:32:01 -04:00
}
2014-03-14 14:13:41 -04:00
return new XcodeSourceFile ( InitFilePath , InitProjectSubFolder ) ;
}
/ * *
* Generates bodies of all sections that contain a list of source files plus a dictionary of project navigator groups .
* /
public void GenerateSectionsContents ( ref string PBXBuildFileSection , ref string PBXFileReferenceSection ,
2014-04-30 09:53:25 -04:00
ref string PBXSourcesBuildPhaseSection , ref Dictionary < string , XcodeFileGroup > Groups )
2014-03-14 14:13:41 -04:00
{
2014-04-30 09:53:25 -04:00
StringBuilder FileSection = new StringBuilder ( ) ;
StringBuilder ReferenceSection = new StringBuilder ( ) ;
StringBuilder BuildPhaseSection = new StringBuilder ( ) ;
2014-03-14 14:13:41 -04:00
foreach ( var CurSourceFile in SourceFiles )
{
XcodeSourceFile SourceFile = CurSourceFile as XcodeSourceFile ;
string FileName = Path . GetFileName ( SourceFile . FilePath ) ;
string FileExtension = Path . GetExtension ( FileName ) ;
string FilePath = Utils . MakePathRelativeTo ( SourceFile . FilePath , XcodeProjectFileGenerator . MasterProjectRelativePath ) ;
string FilePathMac = Utils . CleanDirectorySeparators ( FilePath , '/' ) ;
// Xcode doesn't parse the project file correctly if the build files don't use the same file reference ID,
// so you can't just manually add an entry for UE4CmdLineRun.m, because the ID will conflict with the
// ID that gets generated above implicitly by "XcodeSourceFile SourceFile = CurSourceFile as XcodeSourceFile;"
// At the same time, it is necessary to manually add an entry for it, because we're compiling the unit test with
// exactly one source file.
// This is ugly, but the project file generator wasn't designed to handle special cases like this,
// so we're left with little choice.
if ( FileName = = "UE4CmdLineRun.m" )
{
SourceFile . ReplaceGUIDS ( UE4CmdLineRunFileGuid , UE4CmdLineRunFileRefGuid ) ;
}
2014-04-30 09:53:25 -04:00
if ( IsGeneratedProject )
2014-03-14 14:13:41 -04:00
{
2014-04-30 09:53:25 -04:00
FileSection . Append ( string . Format ( "\t\t{0} /* {1} in {2} */ = {{isa = PBXBuildFile; fileRef = {3} /* {1} */; }};" + ProjectFileGenerator . NewLine ,
SourceFile . FileGUID ,
FileName ,
GetFileCategory ( FileExtension ) ,
SourceFile . FileRefGUID ) ) ;
2014-03-14 14:13:41 -04:00
}
2014-04-30 09:53:25 -04:00
ReferenceSection . Append ( string . Format ( "\t\t{0} /* {1} */ = {{isa = PBXFileReference; lastKnownFileType = {2}; name = \"{1}\"; path = \"{3}\"; sourceTree = SOURCE_ROOT; }};" + ProjectFileGenerator . NewLine ,
SourceFile . FileRefGUID ,
FileName ,
GetFileType ( FileExtension ) ,
FilePathMac ) ) ;
2014-03-14 14:13:41 -04:00
2014-04-23 17:32:01 -04:00
if ( IsPartOfUE4XcodeHelperTarget ( SourceFile ) )
2014-03-14 14:13:41 -04:00
{
2014-04-30 09:53:25 -04:00
BuildPhaseSection . Append ( "\t\t\t\t" + SourceFile . FileGUID + " /* " + FileName + " in Sources */," + ProjectFileGenerator . NewLine ) ;
2014-03-14 14:13:41 -04:00
}
string GroupPath = Path . GetFullPath ( Path . GetDirectoryName ( SourceFile . FilePath ) ) ;
XcodeFileGroup Group = FindGroupByFullPath ( ref Groups , GroupPath ) ;
if ( Group ! = null )
{
Group . Files . Add ( SourceFile ) ;
}
}
2014-04-30 09:53:25 -04:00
PBXBuildFileSection + = FileSection . ToString ( ) ;
PBXFileReferenceSection + = ReferenceSection . ToString ( ) ;
PBXSourcesBuildPhaseSection + = BuildPhaseSection . ToString ( ) ;
2014-03-14 14:13:41 -04:00
}
}
}