2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-12-06 19:14:20 -05:00
using System ;
2014-03-14 14:13:41 -04:00
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.IO ;
namespace UnrealBuildTool
{
2014-05-14 06:42:35 -04:00
/// <summary>
/// Exception when parsing ini files
/// </summary>
public class IniParsingException : Exception
{
public IniParsingException ( string Message )
: base ( Message )
2015-09-24 12:37:21 -04:00
{ }
2014-05-14 06:42:35 -04:00
public IniParsingException ( string Format , params object [ ] Args )
: base ( String . Format ( Format , Args ) )
2015-09-24 12:37:21 -04:00
{ }
2014-05-14 06:42:35 -04:00
}
/// <summary>
/// Equivalent of FConfigCacheIni. Parses ini files.
/// </summary>
public class ConfigCacheIni
2014-03-14 14:13:41 -04:00
{
2015-09-15 09:29:42 -04:00
// command class for being able to create config caches over and over without needing to read the ini files
public class Command
{
public string TrimmedLine ;
}
class SectionCommand : Command
{
public FileReference Filename ;
public int LineIndex ;
}
class KeyValueCommand : Command
{
public string Key ;
public string Value ;
public ParseAction LastAction ;
}
// cached ini files
2015-09-24 12:37:21 -04:00
static Dictionary < string , List < Command > > FileCache = new Dictionary < string , List < Command > > ( ) ;
2015-09-15 09:29:42 -04:00
static Dictionary < string , ConfigCacheIni > IniCache = new Dictionary < string , ConfigCacheIni > ( ) ;
static Dictionary < string , ConfigCacheIni > BaseIniCache = new Dictionary < string , ConfigCacheIni > ( ) ;
2015-09-24 12:25:10 -04:00
static List < string > RequiredSections = new List < string > ( ) { "AppxManifest" , "CommonSettings" , "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "/Script/AndroidPlatformEditor.AndroidSDKSettings" ,
2015-09-15 17:05:07 -04:00
"/Script/BuildSettings.BuildSettings" , "/Script/IOSRuntimeSettings.IOSRuntimeSettings" , "/Script/WindowsTargetPlatform.WindowsTargetSettings" ,
"/Script/UnrealEd.ProjectPackagingSettings" , "/Script/PS4PlatformEditor.PS4TargetSettings" , "/Script/XboxOneTargetPlatform.XboxOneTargetSettings" ,
"/Script/HTML5PlatformEditor.HTML5TargetSettings" } ;
2015-09-15 09:29:42 -04:00
// static creation functions for ini files
public static ConfigCacheIni CreateConfigCacheIni ( UnrealTargetPlatform Platform , string BaseIniName , DirectoryReference ProjectDirectory , DirectoryReference EngineDirectory = null )
{
if ( EngineDirectory = = null )
{
EngineDirectory = UnrealBuildTool . EngineDirectory ;
}
// cache base ini for use as the seed for the rest
if ( ! BaseIniCache . ContainsKey ( BaseIniName ) )
{
2015-09-24 12:37:21 -04:00
BaseIniCache . Add ( BaseIniName , new ConfigCacheIni ( UnrealTargetPlatform . Unknown , BaseIniName , null , EngineDirectory , EngineOnly : true ) ) ;
2015-09-15 09:29:42 -04:00
}
// build the new ini and cache it for later re-use
ConfigCacheIni BaseCache = BaseIniCache [ BaseIniName ] ;
string Key = GetIniPlatformName ( Platform ) + BaseIniName + EngineDirectory . FullName + ( ProjectDirectory ! = null ? ProjectDirectory . FullName : "" ) ;
if ( ! IniCache . ContainsKey ( Key ) )
{
2015-09-24 12:37:21 -04:00
IniCache . Add ( Key , new ConfigCacheIni ( Platform , BaseIniName , ProjectDirectory , EngineDirectory , BaseCache : BaseCache ) ) ;
2015-09-15 09:29:42 -04:00
}
return IniCache [ Key ] ;
}
2014-03-14 14:13:41 -04:00
/// <summary>
2014-05-14 06:42:35 -04:00
/// List of values (or a single value)
2014-03-14 14:13:41 -04:00
/// </summary>
2014-05-14 06:42:35 -04:00
public class IniValues : List < string >
2014-03-14 14:13:41 -04:00
{
2014-05-14 06:42:35 -04:00
public override string ToString ( )
{
return String . Join ( "," , ToArray ( ) ) ;
}
}
/// <summary>
/// Ini section (map of keys and values)
/// </summary>
public class IniSection : Dictionary < string , IniValues >
{
public IniSection ( )
: base ( StringComparer . InvariantCultureIgnoreCase )
2015-09-24 12:37:21 -04:00
{ }
2014-05-14 06:42:35 -04:00
public override string ToString ( )
{
return "IniSection" ;
}
}
2015-09-24 12:25:10 -04:00
/// <summary>
/// True if we are loading a hierarchy of config files that should be merged together
/// </summary>
bool bIsMergingConfigs ;
2014-05-14 06:42:35 -04:00
/// <summary>
/// All sections parsed from ini file
/// </summary>
Dictionary < string , IniSection > Sections ;
2015-09-24 12:25:10 -04:00
/// <summary>
/// Constructor. Parses a single ini file. No Platform settings, no engine hierarchy. Do not use this with ini files that have hierarchy!
/// </summary>
/// <param name="Filename">The ini file to load</param>
public ConfigCacheIni ( FileReference Filename )
{
Init ( Filename ) ;
}
2014-10-27 17:53:23 -04:00
/// <summary>
/// Constructor. Parses ini hierarchy for the specified project. No Platform settings.
/// </summary>
/// <param name="ProjectDirectory">Project path</param>
/// <param name="Platform">Target platform</param>
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param>
public ConfigCacheIni ( string BaseIniName , string ProjectDirectory , string EngineDirectory = null )
2015-09-03 08:47:24 -04:00
{
2015-09-24 12:37:21 -04:00
Init ( UnrealTargetPlatform . Unknown , BaseIniName , ( ProjectDirectory = = null ) ? null : new DirectoryReference ( ProjectDirectory ) , ( EngineDirectory = = null ) ? null : new DirectoryReference ( EngineDirectory ) ) ;
2015-09-03 08:47:24 -04:00
}
/// <summary>
/// Constructor. Parses ini hierarchy for the specified project. No Platform settings.
/// </summary>
/// <param name="ProjectDirectory">Project path</param>
/// <param name="Platform">Target platform</param>
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param>
public ConfigCacheIni ( string BaseIniName , DirectoryReference ProjectDirectory , DirectoryReference EngineDirectory = null )
2014-10-27 17:53:23 -04:00
{
Init ( UnrealTargetPlatform . Unknown , BaseIniName , ProjectDirectory , EngineDirectory ) ;
}
2014-05-14 06:42:35 -04:00
/// <summary>
/// Constructor. Parses ini hierarchy for the specified platform and project.
/// </summary>
/// <param name="ProjectDirectory">Project path</param>
/// <param name="Platform">Target platform</param>
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param>
public ConfigCacheIni ( UnrealTargetPlatform Platform , string BaseIniName , string ProjectDirectory , string EngineDirectory = null )
2015-09-03 08:47:24 -04:00
{
2015-09-24 12:37:21 -04:00
Init ( Platform , BaseIniName , ( ProjectDirectory = = null ) ? null : new DirectoryReference ( ProjectDirectory ) , ( EngineDirectory = = null ) ? null : new DirectoryReference ( EngineDirectory ) ) ;
2015-09-03 08:47:24 -04:00
}
/// <summary>
/// Constructor. Parses ini hierarchy for the specified platform and project.
/// </summary>
/// <param name="ProjectDirectory">Project path</param>
/// <param name="Platform">Target platform</param>
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param>
2015-09-15 09:29:42 -04:00
public ConfigCacheIni ( UnrealTargetPlatform Platform , string BaseIniName , DirectoryReference ProjectDirectory , DirectoryReference EngineDirectory = null , bool EngineOnly = false , ConfigCacheIni BaseCache = null )
2014-10-27 17:53:23 -04:00
{
2015-09-15 09:29:42 -04:00
Init ( Platform , BaseIniName , ProjectDirectory , EngineDirectory , EngineOnly , BaseCache ) ;
2014-10-27 17:53:23 -04:00
}
2015-09-24 12:25:10 -04:00
private void InitCommon ( )
2014-05-14 06:42:35 -04:00
{
Sections = new Dictionary < string , IniSection > ( StringComparer . InvariantCultureIgnoreCase ) ;
2015-09-24 12:25:10 -04:00
}
private void Init ( FileReference IniFileName )
{
InitCommon ( ) ;
bIsMergingConfigs = false ;
ParseIniFile ( IniFileName ) ;
}
private void Init ( UnrealTargetPlatform Platform , string BaseIniName , DirectoryReference ProjectDirectory , DirectoryReference EngineDirectory , bool EngineOnly = false , ConfigCacheIni BaseCache = null )
{
InitCommon ( ) ;
bIsMergingConfigs = true ;
2015-09-03 08:47:24 -04:00
if ( EngineDirectory = = null )
2014-05-14 06:42:35 -04:00
{
2015-09-03 08:47:24 -04:00
EngineDirectory = UnrealBuildTool . EngineDirectory ;
2014-05-14 06:42:35 -04:00
}
2014-10-27 17:53:23 -04:00
2015-09-15 09:29:42 -04:00
if ( BaseCache ! = null )
2014-03-14 14:13:41 -04:00
{
2015-09-24 12:37:21 -04:00
foreach ( var Pair in BaseCache . Sections )
2014-03-14 14:13:41 -04:00
{
2015-09-15 09:29:42 -04:00
Sections . Add ( Pair . Key , Pair . Value ) ;
}
}
if ( EngineOnly )
{
foreach ( var IniFileName in EnumerateEngineIniFileNames ( EngineDirectory , BaseIniName ) )
{
if ( IniFileName . Exists ( ) )
{
ParseIniFile ( IniFileName ) ;
}
}
}
else
{
foreach ( var IniFileName in EnumerateCrossPlatformIniFileNames ( ProjectDirectory , EngineDirectory , Platform , BaseIniName , BaseCache ! = null ) )
{
if ( IniFileName . Exists ( ) )
{
ParseIniFile ( IniFileName ) ;
}
2014-03-14 14:13:41 -04:00
}
}
}
2014-05-14 06:42:35 -04:00
/// <summary>
/// Finds a section in INI
/// </summary>
/// <param name="SectionName"></param>
/// <returns>Found section or null</returns>
public IniSection FindSection ( string SectionName )
{
IniSection Section ;
Sections . TryGetValue ( SectionName , out Section ) ;
return Section ;
}
/// <summary>
/// Finds values associated with the specified key (does not copy the list)
/// </summary>
private bool GetList ( string SectionName , string Key , out IniValues Value )
{
bool Result = false ;
var Section = FindSection ( SectionName ) ;
Value = null ;
if ( Section ! = null )
{
if ( Section . TryGetValue ( Key , out Value ) )
{
Result = true ;
}
}
return Result ;
}
/// <summary>
/// Gets all values associated with the specified key
/// </summary>
/// <param name="SectionName">Section where the key is located</param>
/// <param name="Key">Key name</param>
/// <param name="Value">Copy of the list containing all values associated with the specified key</param>
/// <returns>True if the key exists</returns>
public bool GetArray ( string SectionName , string Key , out List < string > Value )
{
Value = null ;
IniValues ValueList ;
bool Result = GetList ( SectionName , Key , out ValueList ) ;
if ( Result )
{
Value = new List < string > ( ValueList ) ;
}
return Result ;
}
/// <summary>
/// Gets a single string value associated with the specified key.
/// </summary>
/// <param name="SectionName">Section name</param>
/// <param name="Key">Key name</param>
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param>
/// <returns>True if the key exists</returns>
public bool GetString ( string SectionName , string Key , out string Value )
{
Value = String . Empty ;
IniValues ValueList ;
bool Result = GetList ( SectionName , Key , out ValueList ) ;
if ( Result & & ValueList ! = null & & ValueList . Count > 0 )
{
Value = ValueList [ 0 ] ;
Result = true ;
}
else
{
Result = false ;
}
return Result ;
}
/// <summary>
/// Gets a single bool value associated with the specified key.
/// </summary>
/// <param name="SectionName">Section name</param>
/// <param name="Key">Key name</param>
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param>
/// <returns>True if the key exists</returns>
public bool GetBool ( string SectionName , string Key , out bool Value )
2015-09-24 12:37:21 -04:00
{
2014-05-14 06:42:35 -04:00
Value = false ;
string TextValue ;
bool Result = GetString ( SectionName , Key , out TextValue ) ;
if ( Result )
{
// C# Boolean type expects "False" or "True" but since we're not case sensitive, we need to suppor that manually
2014-09-04 10:24:49 -04:00
if ( String . Compare ( TextValue , "true" , true ) = = 0 | | String . Compare ( TextValue , "1" ) = = 0 )
2014-05-14 06:42:35 -04:00
{
Value = true ;
}
2014-09-04 10:24:49 -04:00
else if ( String . Compare ( TextValue , "false" , true ) = = 0 | | String . Compare ( TextValue , "0" ) = = 0 )
2014-05-14 06:42:35 -04:00
{
Value = false ;
}
else
{
// Failed to parse
Result = false ;
}
}
return Result ;
}
/// <summary>
/// Gets a single Int32 value associated with the specified key.
/// </summary>
/// <param name="SectionName">Section name</param>
/// <param name="Key">Key name</param>
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param>
/// <returns>True if the key exists</returns>
public bool GetInt32 ( string SectionName , string Key , out int Value )
{
Value = 0 ;
string TextValue ;
bool Result = GetString ( SectionName , Key , out TextValue ) ;
if ( Result )
{
Result = Int32 . TryParse ( TextValue , out Value ) ;
}
return Result ;
}
2015-05-07 12:19:58 -04:00
/// <summary>
/// Gets a single GUID value associated with the specified key.
/// </summary>
/// <param name="SectionName">Section name</param>
/// <param name="Key">Key name</param>
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param>
/// <returns>True if the key exists</returns>
public bool GetGUID ( string SectionName , string Key , out Guid Value )
{
Value = Guid . Empty ;
string TextValue ;
bool Result = GetString ( SectionName , Key , out TextValue ) ;
if ( Result )
{
2015-09-24 12:37:21 -04:00
string HexString = "" ;
if ( TextValue . Contains ( "A=" ) & & TextValue . Contains ( "B=" ) & & TextValue . Contains ( "C=" ) & & TextValue . Contains ( "D=" ) )
{
char [ ] Separators = new char [ ] { '(' , ')' , '=' , ',' , ' ' , 'A' , 'B' , 'C' , 'D' } ;
string [ ] ComponentValues = TextValue . Split ( Separators , StringSplitOptions . RemoveEmptyEntries ) ;
if ( ComponentValues . Length = = 4 )
{
for ( int ComponentIndex = 0 ; ComponentIndex < 4 ; ComponentIndex + + )
{
int IntegerValue ;
Result & = Int32 . TryParse ( ComponentValues [ ComponentIndex ] , out IntegerValue ) ;
HexString + = IntegerValue . ToString ( "X8" ) ;
}
}
}
else
{
HexString = TextValue ;
}
2015-05-07 12:19:58 -04:00
2015-09-24 12:37:21 -04:00
try
{
Value = Guid . ParseExact ( HexString , "N" ) ;
Result = true ;
}
catch ( Exception )
{
Result = false ;
}
2015-05-07 12:19:58 -04:00
}
return Result ;
}
2014-05-14 06:42:35 -04:00
/// <summary>
/// Gets a single float value associated with the specified key.
/// </summary>
/// <param name="SectionName">Section name</param>
/// <param name="Key">Key name</param>
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param>
/// <returns>True if the key exists</returns>
public bool GetSingle ( string SectionName , string Key , out float Value )
{
Value = 0.0f ;
string TextValue ;
bool Result = GetString ( SectionName , Key , out TextValue ) ;
if ( Result )
{
Result = Single . TryParse ( TextValue , out Value ) ;
}
return Result ;
}
/// <summary>
/// Gets a single double value associated with the specified key.
/// </summary>
/// <param name="SectionName">Section name</param>
/// <param name="Key">Key name</param>
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param>
/// <returns>True if the key exists</returns>
public bool GetDouble ( string SectionName , string Key , out double Value )
{
Value = 0.0 ;
string TextValue ;
bool Result = GetString ( SectionName , Key , out TextValue ) ;
if ( Result )
{
Result = Double . TryParse ( TextValue , out Value ) ;
}
return Result ;
}
2015-09-24 12:37:21 -04:00
private static bool ExtractPath ( string Source , out string Path )
{
int start = Source . IndexOf ( '"' ) ;
int end = Source . LastIndexOf ( '"' ) ;
if ( start ! = 1 & & end ! = - 1 & & start < end )
{
+ + start ;
Path = Source . Substring ( start , end - start ) ;
return true ;
}
else
{
Path = "" ;
}
2015-01-20 10:05:42 -05:00
2015-09-24 12:37:21 -04:00
return false ;
}
2015-01-20 10:05:42 -05:00
2015-09-24 12:37:21 -04:00
public bool GetPath ( string SectionName , string Key , out string Value )
{
string temp ;
if ( GetString ( SectionName , Key , out temp ) )
{
return ExtractPath ( temp , out Value ) ;
}
else
{
Value = "" ;
}
2015-01-20 10:05:42 -05:00
2015-09-24 12:37:21 -04:00
return false ;
}
2015-01-20 10:05:42 -05:00
2014-05-14 06:42:35 -04:00
/// <summary>
/// List of actions that can be performed on a single line from ini file
/// </summary>
enum ParseAction
{
None ,
New ,
Add ,
Remove
}
/// <summary>
/// Checks what action should be performed on a single line from ini file
/// </summary>
private ParseAction GetActionForLine ( ref string Line )
{
if ( String . IsNullOrEmpty ( Line ) | | Line . StartsWith ( ";" ) | | Line . StartsWith ( "//" ) )
{
return ParseAction . None ;
}
else if ( Line . StartsWith ( "-" ) )
{
Line = Line . Substring ( 1 ) . TrimStart ( ) ;
return ParseAction . Remove ;
}
else if ( Line . StartsWith ( "+" ) )
{
Line = Line . Substring ( 1 ) . TrimStart ( ) ;
return ParseAction . Add ;
}
else
{
2015-09-24 12:25:10 -04:00
// We use Add rather than New when we're not merging config files together in order
// to mimic the behavior of the C++ config cache when loading a single file
return ( bIsMergingConfigs ) ? ParseAction . New : ParseAction . Add ;
2014-05-14 06:42:35 -04:00
}
}
/// <summary>
/// Loads and parses ini file.
/// </summary>
2015-09-03 08:47:24 -04:00
public void ParseIniFile ( FileReference Filename )
2014-05-14 06:42:35 -04:00
{
2014-12-17 11:37:40 -05:00
String [ ] IniLines = null ;
2015-09-15 09:29:42 -04:00
List < Command > Commands = null ;
if ( ! FileCache . ContainsKey ( Filename . FullName ) )
2014-05-14 06:42:35 -04:00
{
2015-09-15 09:29:42 -04:00
try
{
IniLines = File . ReadAllLines ( Filename . FullName ) ;
Commands = new List < Command > ( ) ;
FileCache . Add ( Filename . FullName , Commands ) ;
}
catch ( Exception ex )
{
Console . WriteLine ( "Error reading ini file: " + Filename + " Exception: " + ex . Message ) ;
}
2014-12-17 11:37:40 -05:00
}
2015-09-15 09:29:42 -04:00
else
2014-12-17 11:37:40 -05:00
{
2015-09-15 09:29:42 -04:00
Commands = FileCache [ Filename . FullName ] ;
2014-12-17 11:37:40 -05:00
}
if ( IniLines ! = null )
{
IniSection CurrentSection = null ;
// Line Index for exceptions
var LineIndex = 1 ;
var bMultiLine = false ;
var SingleValue = "" ;
var Key = "" ;
var LastAction = ParseAction . None ;
// Parse each line
foreach ( var Line in IniLines )
{
var TrimmedLine = Line . Trim ( ) ;
// Multiline value support
bool bWasMultiLine = bMultiLine ;
bMultiLine = TrimmedLine . EndsWith ( "\\" ) ;
if ( bMultiLine )
{
TrimmedLine = TrimmedLine . Substring ( 0 , TrimmedLine . Length - 1 ) . TrimEnd ( ) ;
}
if ( ! bWasMultiLine )
{
if ( TrimmedLine . StartsWith ( "[" ) )
{
CurrentSection = FindOrAddSection ( TrimmedLine , Filename , LineIndex ) ;
LastAction = ParseAction . None ;
2015-09-15 09:29:42 -04:00
if ( CurrentSection ! = null )
{
SectionCommand Command = new SectionCommand ( ) ;
Command . Filename = Filename ;
Command . LineIndex = LineIndex ;
Command . TrimmedLine = TrimmedLine ;
Commands . Add ( Command ) ;
}
2014-12-17 11:37:40 -05:00
}
else
{
if ( LastAction ! = ParseAction . None )
{
throw new IniParsingException ( "Parsing new key/value pair when the previous one has not yet been processed ({0}, {1}) in {2}, line {3}: {4}" , Key , SingleValue , Filename , LineIndex , TrimmedLine ) ;
}
// Check if the line is empty or a comment, also remove any +/- markers
LastAction = GetActionForLine ( ref TrimmedLine ) ;
if ( LastAction ! = ParseAction . None )
{
2015-09-24 12:37:21 -04:00
/ * if ( CurrentSection = = null )
{
throw new IniParsingException ( "Trying to parse key/value pair that doesn't belong to any section in {0}, line {1}: {2}" , Filename , LineIndex , TrimmedLine ) ;
} * /
2014-12-17 11:37:40 -05:00
ParseKeyValuePair ( TrimmedLine , Filename , LineIndex , out Key , out SingleValue ) ;
}
}
}
if ( bWasMultiLine )
{
SingleValue + = TrimmedLine ;
}
2015-09-15 09:29:42 -04:00
if ( ! bMultiLine & & LastAction ! = ParseAction . None & & CurrentSection ! = null )
2014-12-17 11:37:40 -05:00
{
ProcessKeyValuePair ( CurrentSection , Key , SingleValue , LastAction ) ;
2015-09-15 09:29:42 -04:00
KeyValueCommand Command = new KeyValueCommand ( ) ;
Command . Key = Key ;
Command . Value = SingleValue ;
Command . LastAction = LastAction ;
Commands . Add ( Command ) ;
2014-12-17 11:37:40 -05:00
LastAction = ParseAction . None ;
SingleValue = "" ;
Key = "" ;
}
2015-09-15 09:29:42 -04:00
else if ( CurrentSection = = null )
{
LastAction = ParseAction . None ;
}
2014-12-17 11:37:40 -05:00
LineIndex + + ;
}
2014-05-14 06:42:35 -04:00
}
2015-09-15 09:29:42 -04:00
else if ( Commands ! = null )
{
IniSection CurrentSection = null ;
// run each command
2015-09-24 12:37:21 -04:00
for ( int Idx = 0 ; Idx < Commands . Count ; + + Idx )
2015-09-15 09:29:42 -04:00
{
var Command = Commands [ Idx ] ;
if ( Command is SectionCommand )
{
CurrentSection = FindOrAddSection ( ( Command as SectionCommand ) . TrimmedLine , ( Command as SectionCommand ) . Filename , ( Command as SectionCommand ) . LineIndex ) ;
}
else if ( Command is KeyValueCommand )
{
ProcessKeyValuePair ( CurrentSection , ( Command as KeyValueCommand ) . Key , ( Command as KeyValueCommand ) . Value , ( Command as KeyValueCommand ) . LastAction ) ;
}
}
}
2014-05-14 06:42:35 -04:00
}
/// <summary>
/// Splits a line into key and value
/// </summary>
2015-09-03 08:47:24 -04:00
private static void ParseKeyValuePair ( string TrimmedLine , FileReference Filename , int LineIndex , out string Key , out string Value )
2015-09-24 12:37:21 -04:00
{
2014-05-14 06:42:35 -04:00
var AssignIndex = TrimmedLine . IndexOf ( '=' ) ;
if ( AssignIndex < 0 )
{
throw new IniParsingException ( "Failed to find value when parsing {0}, line {1}: {2}" , Filename , LineIndex , TrimmedLine ) ;
}
Key = TrimmedLine . Substring ( 0 , AssignIndex ) . Trim ( ) ;
if ( String . IsNullOrEmpty ( Key ) )
{
throw new IniParsingException ( "Empty key when parsing {0}, line {1}: {2}" , Filename , LineIndex , TrimmedLine ) ;
}
Value = TrimmedLine . Substring ( AssignIndex + 1 ) . Trim ( ) ;
if ( Value . StartsWith ( "\"" ) )
{
// Remove quotes
var QuoteEnd = Value . LastIndexOf ( '\"' ) ;
if ( QuoteEnd = = 0 )
{
throw new IniParsingException ( "Mismatched quotes when parsing {0}, line {1}: {2}" , Filename , LineIndex , TrimmedLine ) ;
}
Value = Value . Substring ( 1 , Value . Length - 2 ) ;
}
}
/// <summary>
/// Processes parsed key/value pair
/// </summary>
private static void ProcessKeyValuePair ( IniSection CurrentSection , string Key , string SingleValue , ParseAction Action )
{
switch ( Action )
{
case ParseAction . New :
{
// New/replace
IniValues Value ;
if ( CurrentSection . TryGetValue ( Key , out Value ) = = false )
{
Value = new IniValues ( ) ;
CurrentSection . Add ( Key , Value ) ;
}
Value . Clear ( ) ;
2015-09-24 12:37:21 -04:00
Value . Add ( SingleValue ) ;
2014-05-14 06:42:35 -04:00
}
break ;
case ParseAction . Add :
{
IniValues Value ;
if ( CurrentSection . TryGetValue ( Key , out Value ) = = false )
{
Value = new IniValues ( ) ;
CurrentSection . Add ( Key , Value ) ;
}
Value . Add ( SingleValue ) ;
}
break ;
case ParseAction . Remove :
{
IniValues Value ;
if ( CurrentSection . TryGetValue ( Key , out Value ) )
{
2015-07-24 08:35:03 -04:00
var ExistingIndex = Value . FindIndex ( X = > ( String . Compare ( SingleValue , X , true ) = = 0 ) ) ;
2014-05-14 06:42:35 -04:00
if ( ExistingIndex > = 0 )
{
Value . RemoveAt ( ExistingIndex ) ;
}
}
}
break ;
}
}
/// <summary>
/// Finds an existing section or adds a new one
/// </summary>
2015-09-03 08:47:24 -04:00
private IniSection FindOrAddSection ( string TrimmedLine , FileReference Filename , int LineIndex )
2014-05-14 06:42:35 -04:00
{
var SectionEndIndex = TrimmedLine . IndexOf ( ']' ) ;
if ( SectionEndIndex ! = ( TrimmedLine . Length - 1 ) )
{
throw new IniParsingException ( "Mismatched brackets when parsing section name in {0}, line {1}: {2}" , Filename , LineIndex , TrimmedLine ) ;
}
var SectionName = TrimmedLine . Substring ( 1 , TrimmedLine . Length - 2 ) ;
if ( String . IsNullOrEmpty ( SectionName ) )
{
throw new IniParsingException ( "Empty section name when parsing {0}, line {1}: {2}" , Filename , LineIndex , TrimmedLine ) ;
}
2015-09-15 09:29:42 -04:00
else if ( RequiredSections . Contains ( SectionName ) )
2014-05-14 06:42:35 -04:00
{
2015-09-15 09:29:42 -04:00
IniSection CurrentSection ;
if ( Sections . TryGetValue ( SectionName , out CurrentSection ) = = false )
{
CurrentSection = new IniSection ( ) ;
Sections . Add ( SectionName , CurrentSection ) ;
}
return CurrentSection ;
}
return null ;
2014-05-14 06:42:35 -04:00
}
2014-03-14 14:13:41 -04:00
/// <summary>
2015-09-15 09:29:42 -04:00
/// Returns a list of INI filenames for the engine
2014-03-14 14:13:41 -04:00
/// </summary>
2015-09-15 09:29:42 -04:00
private static IEnumerable < FileReference > EnumerateEngineIniFileNames ( DirectoryReference EngineDirectory , string BaseIniName )
2014-03-14 14:13:41 -04:00
{
// Engine/Config/Base.ini (included in every ini type, required)
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( EngineDirectory , "Config" , "Base.ini" ) ;
2014-03-14 14:13:41 -04:00
// Engine/Config/Base* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( EngineDirectory , "Config" , "Base" + BaseIniName + ".ini" ) ;
2014-03-14 14:13:41 -04:00
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
// Engine/Config/NotForLicensees/Base* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( EngineDirectory , "Config" , "NotForLicensees" , "Base" + BaseIniName + ".ini" ) ;
2015-09-15 09:29:42 -04:00
}
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
2015-09-15 09:29:42 -04:00
/// <summary>
/// Returns a list of INI filenames for the given project
/// </summary>
private static IEnumerable < FileReference > EnumerateCrossPlatformIniFileNames ( DirectoryReference ProjectDirectory , DirectoryReference EngineDirectory , UnrealTargetPlatform Platform , string BaseIniName , bool SkipEngine )
{
if ( ! SkipEngine )
{
// Engine/Config/Base.ini (included in every ini type, required)
yield return FileReference . Combine ( EngineDirectory , "Config" , "Base.ini" ) ;
// Engine/Config/Base* ini
yield return FileReference . Combine ( EngineDirectory , "Config" , "Base" + BaseIniName + ".ini" ) ;
// Engine/Config/NotForLicensees/Base* ini
yield return FileReference . Combine ( EngineDirectory , "Config" , "NotForLicensees" , "Base" + BaseIniName + ".ini" ) ;
// NOTE: 4.7: See comment in GetSourceIniHierarchyFilenames()
// Engine/Config/NoRedist/Base* ini
// yield return Path.Combine(EngineDirectory, "Config", "NoRedist", "Base" + BaseIniName + ".ini");
}
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
2015-09-24 12:37:21 -04:00
if ( ProjectDirectory ! = null )
2015-01-20 10:05:42 -05:00
{
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
// Game/Config/Default* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( ProjectDirectory , "Config" , "Default" + BaseIniName + ".ini" ) ;
2015-09-24 12:37:21 -04:00
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
// Game/Config/NotForLicensees/Default* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( ProjectDirectory , "Config" , "NotForLicensees" , "Default" + BaseIniName + ".ini" ) ;
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
// Game/Config/NoRedist/Default* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( ProjectDirectory , "Config" , "NoRedist" , "Default" + BaseIniName + ".ini" ) ;
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
}
2015-09-24 12:37:21 -04:00
string PlatformName = GetIniPlatformName ( Platform ) ;
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
if ( Platform ! = UnrealTargetPlatform . Unknown )
{
// Engine/Config/Platform/Platform* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( EngineDirectory , "Config" , PlatformName , PlatformName + BaseIniName + ".ini" ) ;
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
2015-09-24 12:37:21 -04:00
if ( ProjectDirectory ! = null )
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
{
// Game/Config/Platform/Platform* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( ProjectDirectory , "Config" , PlatformName , PlatformName + BaseIniName + ".ini" ) ;
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
}
}
2015-09-03 08:47:24 -04:00
DirectoryReference UserSettingsFolder = Utils . GetUserSettingDirectory ( ) ; // Match FPlatformProcess::UserSettingsDir()
DirectoryReference PersonalFolder = null ; // Match FPlatformProcess::UserDir()
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
if ( BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Mac )
2014-12-10 09:07:36 -05:00
{
2015-09-03 08:47:24 -04:00
PersonalFolder = new DirectoryReference ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) , "Documents" ) ) ;
2014-12-10 09:07:36 -05:00
}
else if ( Environment . OSVersion . Platform = = PlatformID . Unix )
{
2015-09-03 08:47:24 -04:00
PersonalFolder = new DirectoryReference ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) , "Documents" ) ) ;
2014-12-10 09:07:36 -05:00
}
else
{
2015-09-03 08:47:24 -04:00
PersonalFolder = new DirectoryReference ( Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) ) ;
2014-12-10 09:07:36 -05:00
}
// <AppData>/UE4/EngineConfig/User* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( UserSettingsFolder , "Unreal Engine" , "Engine" , "Config" , "User" + BaseIniName + ".ini" ) ;
2014-12-10 09:07:36 -05:00
// <Documents>/UE4/EngineConfig/User* ini
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( PersonalFolder , "Unreal Engine" , "Engine" , "Config" , "User" + BaseIniName + ".ini" ) ;
2015-09-24 12:37:21 -04:00
2014-12-10 09:07:36 -05:00
// Game/Config/User* ini
2015-09-24 12:37:21 -04:00
if ( ProjectDirectory ! = null )
{
2015-09-03 08:47:24 -04:00
yield return FileReference . Combine ( ProjectDirectory , "Config" , "User" + BaseIniName + ".ini" ) ;
- Generate AndroidManifest.xml from ProjectSettings
- Moved some Android settings to ProjectSettings, re-enabled SDK settings
- Removed SigningConfig.xml, and moved those settings into project settings
- Added concept of NotForLicensees and NoRedist engine and project config settings
- Removed BaseInternalGame.ini, replaced with NotForLicensees/BaseGame.ini
- Moved User*.ini to end of .ini hierarchy
- Added support for CLASS_GlobalUserConfig, so their settings will be saved to <AppData>/.../User*.ini (useful for SDK paths, etc)
- Enabled AndroidPlatformEditor module on Mac
- Changed Mac Build.sh to allow for Android on the commandline (just pass through if it's not an Xcode platform name)
- Iterative Android packaging now looks at just the important .ini sections, NOT entire .ini files
#codereview jamie.dale,james.moran,michael.trepka,robert.jones,chris.babcock
[CL 2413870 by Josh Adams in Main branch]
2015-01-21 11:17:55 -05:00
}
2014-10-27 17:53:23 -04:00
}
2014-03-14 14:13:41 -04:00
/// <summary>
/// Returns the platform name to use as part of platform-specific config files
/// </summary>
private static string GetIniPlatformName ( UnrealTargetPlatform TargetPlatform )
{
2015-09-24 12:37:21 -04:00
if ( TargetPlatform = = UnrealTargetPlatform . Win32 | | TargetPlatform = = UnrealTargetPlatform . Win64 | | TargetPlatform = = UnrealTargetPlatform . UWP )
2014-03-14 14:13:41 -04:00
{
return "Windows" ;
}
else
{
return Enum . GetName ( typeof ( UnrealTargetPlatform ) , TargetPlatform ) ;
}
}
}
}