2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.IO ;
using System.Threading ;
using System.Diagnostics ;
using System.Reflection ;
using UnrealBuildTool ;
using System.Text.RegularExpressions ;
namespace AutomationTool
{
#region UAT Internal Utils
/// <summary>
/// AutomationTool internal Utilities.
/// </summary>
public static class InternalUtils
{
/// <summary>
/// Gets environment variable value.
/// </summary>
/// <param name="VarName">Variable name.</param>
/// <param name="Default">Default value to be returned if the variable does not exist.</param>
/// <returns>Variable value or the default value if the variable did not exist.</returns>
public static string GetEnvironmentVariable ( string VarName , string Default , bool bQuiet = false )
{
var Value = Environment . GetEnvironmentVariable ( VarName ) ;
if ( Value = = null )
{
Value = Default ;
}
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "GetEnvironmentVariable {0}={1}" , VarName , Value ) ;
2014-03-14 14:13:41 -04:00
}
return Value ;
}
/// <summary>
/// Creates a directory.
/// </summary>
/// <param name="Path">Directory name.</param>
/// <returns>True if the directory was created, false otherwise.</returns>
public static bool SafeCreateDirectory ( string Path , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeCreateDirectory {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
}
bool Result = true ;
try
{
Result = Directory . CreateDirectory ( Path ) . Exists ;
}
catch ( Exception )
{
if ( Directory . Exists ( Path ) = = false )
{
Result = false ;
}
}
return Result ;
}
/// <summary>
/// Deletes a file (will remove read-only flag if necessary).
/// </summary>
/// <param name="Path">Filename</param>
/// <param name="bQuiet">if true, then do not print errors, also in quiet mode do not retry</param>
/// <returns>True if the file does not exist, false otherwise.</returns>
public static bool SafeDeleteFile ( string Path , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeDeleteFile {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
}
int MaxAttempts = bQuiet ? 1 : 10 ;
int Attempts = 0 ;
bool Result = true ;
Exception LastException = null ;
do
{
Result = true ;
try
{
if ( File . Exists ( Path ) )
{
FileAttributes Attributes = File . GetAttributes ( Path ) ;
if ( ( Attributes & FileAttributes . ReadOnly ) ! = 0 )
{
File . SetAttributes ( Path , Attributes & ~ FileAttributes . ReadOnly ) ;
}
File . Delete ( Path ) ;
}
}
catch ( Exception Ex )
{
if ( File . Exists ( Path ) )
{
Result = false ;
}
LastException = Ex ;
}
if ( Result = = false & & Attempts + 1 < MaxAttempts )
{
System . Threading . Thread . Sleep ( 1000 ) ;
}
} while ( Result = = false & & + + Attempts < MaxAttempts ) ;
if ( Result = = false & & LastException ! = null )
{
if ( bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "Failed to delete file {0} in {1} attempts." , Path , MaxAttempts ) ;
2014-03-14 14:13:41 -04:00
}
else
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Failed to delete file {0} in {1} attempts." , Path , MaxAttempts ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( LastException ) ) ;
2014-03-14 14:13:41 -04:00
}
}
return Result ;
}
/// <summary>
/// Recursively deletes a directory and all its files and subdirectories.
/// </summary>
/// <param name="Path">Path to delete.</param>
/// <returns>Whether the deletion was succesfull.</returns>
private static bool RecursivelyDeleteDirectory ( string Path , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "RecursivelyDeleteDirectory {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
}
// Delete all files. This will also delete read-only files.
var FilesInDirectory = Directory . EnumerateFiles ( Path ) ;
foreach ( string Filename in FilesInDirectory )
{
if ( SafeDeleteFile ( Filename , bQuiet ) = = false )
{
return false ;
}
}
// Recursively delete all files from sub-directories.
var FoldersInDirectory = Directory . EnumerateDirectories ( Path ) ;
foreach ( string Folder in FoldersInDirectory )
{
if ( RecursivelyDeleteDirectory ( Folder , bQuiet ) = = false )
{
return false ;
}
}
// At this point there should be no read-only files in any of the directories and
// this directory should be empty too.
return SafeDeleteEmptyDirectory ( Path , bQuiet ) ;
}
/// <summary>
/// Deletes an empty directory.
/// </summary>
/// <param name="Path">Path to the Directory.</param>
/// <returns>True if deletion was successful, otherwise false.</returns>
public static bool SafeDeleteEmptyDirectory ( string Path , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeDeleteEmptyDirectory {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
}
const int MaxAttempts = 10 ;
int Attempts = 0 ;
bool Result = true ;
Exception LastException = null ;
do
{
Result = ! Directory . Exists ( Path ) ;
if ( ! Result )
{
try
{
Directory . Delete ( Path , true ) ;
}
catch ( Exception Ex )
{
if ( Directory . Exists ( Path ) )
{
Thread . Sleep ( 3000 ) ;
}
Result = ! Directory . Exists ( Path ) ;
LastException = Ex ;
}
}
} while ( Result = = false & & + + Attempts < MaxAttempts ) ;
if ( Result = = false & & LastException ! = null )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Failed to delete directory {0} in {1} attempts." , Path , MaxAttempts ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( LastException ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Deletes a directory and all its contents. Will delete read-only files.
/// </summary>
/// <param name="Path">Directory name.</param>
/// <returns>True if the directory no longer exists, false otherwise.</returns>
public static bool SafeDeleteDirectory ( string Path , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeDeleteDirectory {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
}
if ( Directory . Exists ( Path ) )
{
return RecursivelyDeleteDirectory ( Path , bQuiet ) ;
}
else
{
return true ;
}
}
/// <summary>
/// Renames/moves a file.
/// </summary>
/// <param name="OldName">Old name</param>
/// <param name="NewName">New name</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public static bool SafeRenameFile ( string OldName , string NewName , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeRenameFile {0} {1}" , OldName , NewName ) ;
2014-03-14 14:13:41 -04:00
}
const int MaxAttempts = 10 ;
int Attempts = 0 ;
bool Result = true ;
do
{
Result = true ;
try
{
if ( File . Exists ( OldName ) )
{
FileAttributes Attributes = File . GetAttributes ( OldName ) ;
if ( ( Attributes & FileAttributes . ReadOnly ) ! = 0 )
{
File . SetAttributes ( OldName , Attributes & ~ FileAttributes . ReadOnly ) ;
}
}
File . Move ( OldName , NewName ) ;
}
catch ( Exception Ex )
{
if ( File . Exists ( OldName ) = = true | | File . Exists ( NewName ) = = false )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Failed to rename {0} to {1}" , OldName , NewName ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
Result = false ;
}
}
}
while ( Result = = false & & + + Attempts < MaxAttempts ) ;
return Result ;
}
2015-02-05 15:19:39 -05:00
// @todo: This could be passed in from elsewhere, and this should be somehow done per ini section
// but this will get it so that games won't ship passwords
private static string [ ] LinesToFilter = new string [ ]
{
"KeyStorePassword" ,
2015-02-10 12:24:14 -05:00
"KeyPassword" ,
2015-02-05 15:19:39 -05:00
} ;
private static void FilterIniFile ( string SourceName , string TargetName )
{
string [ ] Lines = File . ReadAllLines ( SourceName ) ;
StringBuilder NewLines = new StringBuilder ( "" ) ;
foreach ( string Line in Lines )
{
// look for each filter on each line
bool bFiltered = false ;
foreach ( string Filter in LinesToFilter )
{
if ( Line . StartsWith ( Filter + "=" ) )
{
bFiltered = true ;
break ;
}
}
// write out if it's not filtered out
if ( ! bFiltered )
{
NewLines . AppendLine ( Line ) ;
}
}
// now write out the final .ini file
if ( File . Exists ( TargetName ) )
{
File . Delete ( TargetName ) ;
}
File . WriteAllText ( TargetName , NewLines . ToString ( ) ) ;
// other code assumes same timestamp for source and dest
File . SetLastWriteTimeUtc ( TargetName , File . GetLastWriteTimeUtc ( SourceName ) ) ;
}
2014-03-14 14:13:41 -04:00
/// <summary>
/// Copies a file.
/// </summary>
/// <param name="SourceName">Source name</param>
/// <param name="TargetName">Target name</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
2015-02-05 15:19:39 -05:00
public static bool SafeCopyFile ( string SourceName , string TargetName , bool bQuiet = false , bool bFilterSpecialLinesFromIniFiles = false )
2014-03-14 14:13:41 -04:00
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeCopyFile {0} {1}" , SourceName , TargetName ) ;
2014-03-14 14:13:41 -04:00
}
const int MaxAttempts = 10 ;
int Attempts = 0 ;
bool Result = true ;
do
{
Result = true ;
2015-01-06 13:57:28 -05:00
bool Retry = true ;
2014-03-14 14:13:41 -04:00
try
{
2015-02-05 15:19:39 -05:00
bool bSkipSizeCheck = false ;
if ( bFilterSpecialLinesFromIniFiles & & Path . GetExtension ( SourceName ) = = ".ini" )
{
FilterIniFile ( SourceName , TargetName ) ;
// ini files may change size, don't check
bSkipSizeCheck = true ;
}
else
{
File . Copy ( SourceName , TargetName , overwrite : true ) ;
}
2015-01-06 13:57:28 -05:00
Retry = ! File . Exists ( TargetName ) ;
if ( ! Retry )
{
FileInfo SourceInfo = new FileInfo ( SourceName ) ;
FileInfo TargetInfo = new FileInfo ( TargetName ) ;
2015-02-05 15:19:39 -05:00
if ( ! bSkipSizeCheck & & SourceInfo . Length ! = TargetInfo . Length )
2015-01-06 13:57:28 -05:00
{
Log . WriteLine ( TraceEventType . Warning , "Size mismatch {0} = {1} to {2} = {3}" , SourceName , SourceInfo . Length , TargetName , TargetInfo . Length ) ;
Retry = true ;
}
// Timestamps should be no more than 2 seconds out - assuming this as exFAT filesystems store timestamps at 2 second intervals:
// http://ntfs.com/exfat-time-stamp.htm
if ( ! ( ( SourceInfo . LastWriteTimeUtc - TargetInfo . LastWriteTimeUtc ) . TotalSeconds < 2 & & ( SourceInfo . LastWriteTimeUtc - TargetInfo . LastWriteTimeUtc ) . TotalSeconds > - 2 ) )
{
Log . WriteLine ( TraceEventType . Warning , "Date mismatch {0} = {1} to {2} = {3}" , SourceName , SourceInfo . LastWriteTimeUtc , TargetName , TargetInfo . LastWriteTimeUtc ) ;
Retry = true ;
}
}
}
2014-03-14 14:13:41 -04:00
catch ( Exception Ex )
{
2015-01-06 13:57:28 -05:00
Log . WriteLine ( System . Diagnostics . TraceEventType . Warning , "SafeCopyFile Exception was {0}" , LogUtils . FormatException ( Ex ) ) ;
Retry = true ;
2014-03-14 14:13:41 -04:00
}
2014-07-15 06:54:22 -04:00
2015-01-06 13:57:28 -05:00
if ( Retry )
{
if ( Attempts + 1 < MaxAttempts )
{
Log . WriteLine ( TraceEventType . Warning , "Failed to copy {0} to {1}, deleting, waiting 10s and retrying." , SourceName , TargetName ) ;
if ( File . Exists ( TargetName ) )
{
SafeDeleteFile ( TargetName ) ;
}
Thread . Sleep ( 10000 ) ;
}
else
{
Log . WriteLine ( TraceEventType . Warning , "Failed to copy {0} to {1}" , SourceName , TargetName ) ;
}
Result = false ;
}
}
2014-03-14 14:13:41 -04:00
while ( Result = = false & & + + Attempts < MaxAttempts ) ;
return Result ;
}
/// <summary>
/// Reads all lines from a file.
/// </summary>
/// <param name="Filename">Filename</param>
/// <returns>An array containing all lines read from the file or null if the file could not be read.</returns>
public static string [ ] SafeReadAllLines ( string Filename )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeReadAllLines {0}" , Filename ) ;
2014-03-14 14:13:41 -04:00
string [ ] Result = null ;
try
{
Result = File . ReadAllLines ( Filename ) ;
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Failed to load {0}" , Filename ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Reads all text from a file.
/// </summary>
/// <param name="Filename">Filename</param>
/// <returns>String containing all text read from the file or null if the file could not be read.</returns>
public static string SafeReadAllText ( string Filename )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeReadAllLines {0}" , Filename ) ;
2014-03-14 14:13:41 -04:00
string Result = null ;
try
{
Result = File . ReadAllText ( Filename ) ;
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Failed to load {0}" , Filename ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Finds files in the specified path.
/// </summary>
/// <param name="Path">Path</param>
/// <param name="SearchPattern">Search pattern</param>
/// <param name="Recursive">Whether to search recursively or not.</param>
/// <returns>List of all files found (can be empty) or null if the operation failed.</returns>
public static string [ ] FindFiles ( string Path , string SearchPattern , bool Recursive , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "FindFiles {0} {1} {2}" , Path , SearchPattern , Recursive ) ;
2014-03-14 14:13:41 -04:00
}
2014-10-16 19:14:57 -04:00
2015-04-21 20:53:58 -04:00
// On Linux, filter out symlinks since we (usually) create them to fix mispelled case-sensitive filenames in content, and if they aren't filtered,
// UAT picks up both the symlink and the original file and considers them duplicates when packaging (pak files are case-insensitive).
// Windows needs the symlinks though because that's how deduplication works on Windows server,
// see https://answers.unrealengine.com/questions/212888/automated-buildjenkins-failing-due-to-symlink-chec.html
// FIXME: ZFS, JFS and other fs that can be case-insensitive on Linux should use the faster path as well.
if ( UnrealBuildTool . BuildHostPlatform . Current . Platform ! = UnrealTargetPlatform . Linux )
2014-10-16 19:14:57 -04:00
{
2015-04-21 20:53:58 -04:00
return Directory . GetFiles ( Path , SearchPattern , Recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ) ;
}
else
{
List < string > FileNames = new List < string > ( ) ;
DirectoryInfo DirInfo = new DirectoryInfo ( Path ) ;
foreach ( FileInfo File in DirInfo . EnumerateFiles ( SearchPattern , Recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ) )
2014-10-16 19:14:57 -04:00
{
2015-04-21 20:53:58 -04:00
if ( File . Attributes . HasFlag ( FileAttributes . ReparsePoint ) )
2014-10-16 19:14:57 -04:00
{
2015-04-21 20:53:58 -04:00
if ( ! bQuiet )
{
Log . WriteLine ( TraceEventType . Warning , "Ignoring symlink {0}" , File . FullName ) ;
}
continue ;
2014-10-16 19:14:57 -04:00
}
2015-04-21 20:53:58 -04:00
FileNames . Add ( File . FullName ) ;
2014-10-16 19:14:57 -04:00
}
2015-04-21 20:53:58 -04:00
return FileNames . ToArray ( ) ;
2014-10-16 19:14:57 -04:00
}
2014-03-14 14:13:41 -04:00
}
2014-04-02 18:09:23 -04:00
/// <summary>
/// Finds directories in the specified path.
/// </summary>
/// <param name="Path">Path</param>
/// <param name="SearchPattern">Search pattern</param>
/// <param name="Recursive">Whether to search recursively or not.</param>
/// <returns>List of all directories found (can be empty) or null if the operation failed.</returns>
public static string [ ] FindDirectories ( string Path , string SearchPattern , bool Recursive , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "FindDirectories {0} {1} {2}" , Path , SearchPattern , Recursive ) ;
2014-04-02 18:09:23 -04:00
}
return Directory . GetDirectories ( Path , SearchPattern , Recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ) ;
}
2014-03-14 14:13:41 -04:00
/// <summary>
/// Finds files in the specified path.
/// </summary>
/// <param name="Path">Path</param>
/// <param name="SearchPattern">Search pattern</param>
/// <param name="Recursive">Whether to search recursively or not.</param>
/// <returns>List of all files found (can be empty) or null if the operation failed.</returns>
public static string [ ] SafeFindFiles ( string Path , string SearchPattern , bool Recursive , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeFindFiles {0} {1} {2}" , Path , SearchPattern , Recursive ) ;
2014-03-14 14:13:41 -04:00
}
string [ ] Files = null ;
try
{
Files = FindFiles ( Path , SearchPattern , Recursive , bQuiet ) ;
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Unable to Find Files in {0}" , Path ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Files ;
}
2014-04-02 18:09:23 -04:00
/// <summary>
/// Finds directories in the specified path.
/// </summary>
/// <param name="Path">Path</param>
/// <param name="SearchPattern">Search pattern</param>
/// <param name="Recursive">Whether to search recursively or not.</param>
/// <returns>List of all files found (can be empty) or null if the operation failed.</returns>
public static string [ ] SafeFindDirectories ( string Path , string SearchPattern , bool Recursive , bool bQuiet = false )
{
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeFindDirectories {0} {1} {2}" , Path , SearchPattern , Recursive ) ;
2014-04-02 18:09:23 -04:00
}
string [ ] Directories = null ;
try
{
Directories = FindDirectories ( Path , SearchPattern , Recursive , bQuiet ) ;
}
catch ( Exception Ex )
{
Log . WriteLine ( TraceEventType . Warning , "Unable to Find Directories in {0}" , Path ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
}
return Directories ;
}
2014-03-14 14:13:41 -04:00
/// <summary>
/// Checks if a file exists.
/// </summary>
/// <param name="Path">Filename</param>
/// <param name="bQuiet">if true, do not print a message</param>
/// <returns>True if the file exists, false otherwise.</returns>
public static bool SafeFileExists ( string Path , bool bQuiet = false )
{
bool Result = false ;
try
{
Result = File . Exists ( Path ) ;
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeFileExists {0}={1}" , Path , Result ) ;
2014-03-14 14:13:41 -04:00
}
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Unable to check if file {0} exists." , Path ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Checks if a directory exists.
/// </summary>
/// <param name="Path">Directory</param>
/// <param name="bQuiet">if true, no longging</param>
/// <returns>True if the directory exists, false otherwise.</returns>
public static bool SafeDirectoryExists ( string Path , bool bQuiet = false )
{
bool Result = false ;
try
{
Result = Directory . Exists ( Path ) ;
if ( ! bQuiet )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeDirectoryExists {0}={1}" , Path , Result ) ;
2014-03-14 14:13:41 -04:00
}
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Unable to check if directory {0} exists." , Path ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Writes lines to a file.
/// </summary>
/// <param name="Path">Filename</param>
/// <param name="Text">Text</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public static bool SafeWriteAllLines ( string Path , string [ ] Text )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeWriteAllLines {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
bool Result = false ;
try
{
File . WriteAllLines ( Path , Text ) ;
Result = true ;
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Unable to write text to {0}" , Path ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Writes text to a file.
/// </summary>
/// <param name="Path">Filename</param>
/// <param name="Text">Text</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public static bool SafeWriteAllText ( string Path , string Text )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeWriteAllText {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
bool Result = false ;
try
{
File . WriteAllText ( Path , Text ) ;
Result = true ;
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Unable to write text to {0}" , Path ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Writes text to a file.
/// </summary>
/// <param name="Path">Filename</param>
/// <param name="Text">Text</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public static bool SafeWriteAllBytes ( string Path , byte [ ] Bytes )
{
2015-06-16 10:42:46 -04:00
Log . WriteLine ( TraceEventType . Verbose , "SafeWriteAllBytes {0}" , Path ) ;
2014-03-14 14:13:41 -04:00
bool Result = false ;
try
{
File . WriteAllBytes ( Path , Bytes ) ;
Result = true ;
}
catch ( Exception Ex )
{
2014-04-02 18:09:23 -04:00
Log . WriteLine ( TraceEventType . Warning , "Unable to write text to {0}" , Path ) ;
Log . WriteLine ( TraceEventType . Warning , LogUtils . FormatException ( Ex ) ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
/// <summary>
/// Runs the specified delegate checking if this is the only instance of the application.
/// </summary>
/// <param name="Main"></param>
/// <param name="Param"></param>
2015-06-30 11:40:05 -04:00
public static void RunSingleInstance ( System . Action < object > Main , object Param )
2014-03-14 14:13:41 -04:00
{
if ( Environment . GetEnvironmentVariable ( "uebp_UATMutexNoWait" ) = = "1" )
{
2015-06-30 11:40:05 -04:00
Main ( Param ) ;
2014-03-14 14:13:41 -04:00
}
var bCreatedMutex = false ;
var LocationHash = InternalUtils . ExecutingAssemblyLocation . GetHashCode ( ) ;
var MutexName = "Global/" + Path . GetFileNameWithoutExtension ( ExecutingAssemblyLocation ) + "_" + LocationHash . ToString ( ) + "_Mutex" ;
using ( Mutex SingleInstanceMutex = new Mutex ( true , MutexName , out bCreatedMutex ) )
{
if ( ! bCreatedMutex )
{
2015-06-23 11:36:04 -04:00
throw new AutomationException ( "Another instance of {0} is already running." , ExecutingAssemblyLocation ) ;
2014-03-14 14:13:41 -04:00
}
else
{
Log . WriteLine ( TraceEventType . Verbose , "No other instance of {0} is running." , ExecutingAssemblyLocation ) ;
}
2015-06-30 11:40:05 -04:00
Main ( Param ) ;
2014-03-14 14:13:41 -04:00
SingleInstanceMutex . ReleaseMutex ( ) ;
}
}
/// <summary>
/// Path to the executable which runs this code.
/// </summary>
public static string ExecutingAssemblyDirectory
{
get
{
return CommandUtils . CombinePaths ( Path . GetDirectoryName ( ExecutingAssemblyLocation ) ) ;
}
}
/// <summary>
/// Filename of the executable which runs this code.
/// </summary>
public static string ExecutingAssemblyLocation
{
get
{
return CommandUtils . CombinePaths ( new Uri ( System . Reflection . Assembly . GetExecutingAssembly ( ) . CodeBase ) . LocalPath ) ;
}
}
/// <summary>
/// Version info of the executable which runs this code.
/// </summary>
public static FileVersionInfo ExecutableVersion
{
get
{
return FileVersionInfo . GetVersionInfo ( ExecutingAssemblyLocation ) ;
}
}
}
#endregion
#region VersionFileReader
/// <summary>
/// This is to ensure that UAT can produce version strings precisely compatible
/// with FEngineVersion.
///
/// WARNING: If FEngineVersion compatibility changes, this code needs to be updated.
/// </summary>
public class FEngineVersionSupport
{
/// <summary>
/// The version info read from the Version header. The populated fields will be Major, Minor, and Build from the MAJOR, MINOR, and PATCH lines, respectively.
/// Expects lines like:
/// #define APP_MAJOR_VERSION 0
/// #define APP_MINOR_VERSION 0
/// #define APP_PATCH_VERSION 0
/// </summary>
public readonly Version Version ;
/// <summary>
/// The changelist this version is associated with
/// </summary>
public readonly int Changelist ;
/// <summary>
/// The Branch name associated with the version
/// </summary>
public readonly string BranchName ;
/// <summary>
/// Reads a Version.h file, looking for macros that define the MAJOR/MINOR/PATCH version fields. Expected to match the Version.h in the engine.
/// </summary>
/// <param name="Filename">Version.h file to read.</param>
/// <returns>Version that puts the Major/Minor/Patch fields in the Major/Minor/Build fields, respectively.</returns>
public static Version ReadVersionFromFile ( string Filename )
{
var regex = new Regex ( @"#define.+_(?<Type>MAJOR|MINOR|PATCH)_VERSION\s+(?<Value>.+)" , RegexOptions . ExplicitCapture | RegexOptions . IgnoreCase ) ;
var foundElements = new Dictionary < string , int > ( 3 ) ;
foreach ( var line in File . ReadLines ( Filename ) )
{
try
{
var match = regex . Match ( line ) ;
if ( match . Success )
{
foundElements . Add ( match . Groups [ "Type" ] . Value , int . Parse ( match . Groups [ "Value" ] . Value ) ) ;
}
}
catch ( Exception ex )
{
throw new AutomationException ( string . Format ( "Failed to parse line {0} in version file {1}" , line , Filename ) , ex ) ;
}
}
// must find all three parts to accept the version file.
if ( foundElements . Keys . Intersect ( new [ ] { "MAJOR" , "MINOR" , "PATCH" } ) . Count ( ) ! = 3 )
{
throw new AutomationException ( "Failed to find MAJOR, MINOR, and PATCH fields from version file {0}" , Filename ) ;
}
2014-04-02 18:09:23 -04:00
CommandUtils . Log ( "Read {0}.{1}.{2} from {3}" , foundElements [ "MAJOR" ] , foundElements [ "MINOR" ] , foundElements [ "PATCH" ] , Filename ) ;
2014-03-14 14:13:41 -04:00
return new Version ( foundElements [ "MAJOR" ] , foundElements [ "MINOR" ] , foundElements [ "PATCH" ] ) ;
}
/// <summary>
/// Ctor that takes a pre-determined Version. Gets the Changelist and BranchName from the current <see cref="CommandUtils.P4Env"/>.
/// </summary>
2014-04-30 07:58:10 -04:00
/// <param name="InVersion">Predetermined version.</param>
/// <param name="InChangelist">Predetermined changelist (optional)</param>
/// <param name="InBranchName">Predetermined branch name (optional)</param>
public FEngineVersionSupport ( Version InVersion , int InChangelist = - 1 , string InBranchName = null )
2014-03-14 14:13:41 -04:00
{
2014-04-30 07:58:10 -04:00
Version = InVersion ;
if ( InChangelist < = 0 )
{
Changelist = CommandUtils . P4Enabled ? CommandUtils . P4Env . Changelist : 0 ;
}
else
{
Changelist = InChangelist ;
}
if ( String . IsNullOrEmpty ( InBranchName ) )
{
BranchName = CommandUtils . P4Enabled ? CommandUtils . P4Env . BuildRootEscaped : "UnknownBranch" ;
}
else
{
BranchName = InBranchName ;
}
2014-03-14 14:13:41 -04:00
}
/// <summary>
/// Gets a version string compatible with FEngineVersion's native parsing code.
/// </summary>
/// <remarks>
/// The format looks like: Major.Minor.Build-Changelist+BranchName.
/// </remarks>
/// <returns></returns>
public override string ToString ( )
{
return String . Format ( "{0}.{1}.{2}-{3}+{4}" ,
Version . Major ,
Version . Minor ,
Version . Build ,
Changelist . ToString ( "0000000" ) ,
BranchName ) ;
}
2015-05-15 09:31:31 -04:00
public override bool Equals ( object obj )
{
var that = obj as FEngineVersionSupport ;
if ( that = = null )
return false ;
return this . ToString ( ) = = that . ToString ( ) ;
}
public override int GetHashCode ( )
{
return this . ToString ( ) . GetHashCode ( ) ;
}
2014-03-14 14:13:41 -04:00
/// <summary>
/// Ctor initializes with the values from the supplied Version file. The BranchName and CL are also taken from the current <see cref="CommandUtils.P4Env"/>.
/// </summary>
/// <param name="Filename">Full path to the file with the version info.</param>
2014-04-30 07:58:10 -04:00
/// <param name="InChangelist">Predetermined changelist (optional)</param>
/// <param name="InBranchName">Predetermined branch name (optional)</param>
public static FEngineVersionSupport FromVersionFile ( string Filename , int InChangelist = - 1 , string InBranchName = null )
2014-03-14 14:13:41 -04:00
{
2014-04-30 07:58:10 -04:00
return new FEngineVersionSupport ( ReadVersionFromFile ( Filename ) , InChangelist , InBranchName ) ;
2014-03-14 14:13:41 -04:00
}
/// <summary>
/// Creates a <see cref="FEngineVersionSupport"/> from a string that matches the format given in <see cref="ToString"/>.
/// </summary>
/// <param name="versionString">Version string that should match the FEngineVersion::ToString() format.</param>
2014-10-10 05:40:29 -04:00
/// <param name="bAllowVersion">Optional parameter which if set to true, allows version strings with no version number specified.</param>
2014-03-14 14:13:41 -04:00
/// <returns>a new instance with fields initialized to the match those given in the string.</returns>
2014-10-10 05:40:29 -04:00
public static FEngineVersionSupport FromString ( string versionString , bool bAllowNoVersion = false )
2014-03-14 14:13:41 -04:00
{
try
{
2014-10-10 05:40:29 -04:00
if ( bAllowNoVersion & & versionString . StartsWith ( "++depot" ) )
{
// This form of version is used when a product has no major.minor.patch version
// E.g. ++depot+UE4-ProdName-CL-12345678
var clSplit = versionString . Split ( new string [ ] { "-CL-" } , 2 , StringSplitOptions . None ) ;
var dashSplit = clSplit [ 1 ] . Split ( new [ ] { '-' } , 2 ) ;
var changelist = int . Parse ( dashSplit [ 0 ] ) ;
var branchName = clSplit [ 0 ] ;
return new FEngineVersionSupport ( new Version ( 0 , 0 , 0 ) , changelist , branchName ) ;
}
else
{
// This is the standard Rocket versioning scheme, e.g. "4.5.0-12345678+++depot+UE4"
var dotSplit = versionString . Split ( new [ ] { '.' } , 3 ) ;
var dashSplit = dotSplit [ 2 ] . Split ( new [ ] { '-' } , 2 ) ;
var plusSplit = dashSplit [ 1 ] . Split ( new [ ] { '+' } , 2 ) ;
var major = int . Parse ( dotSplit [ 0 ] ) ;
var minor = int . Parse ( dotSplit [ 1 ] ) ;
var patch = int . Parse ( dashSplit [ 0 ] ) ;
var changelist = int . Parse ( plusSplit [ 0 ] ) ;
var branchName = plusSplit [ 1 ] ;
return new FEngineVersionSupport ( new Version ( major , minor , patch ) , changelist , branchName ) ;
}
2014-03-14 14:13:41 -04:00
}
catch ( Exception ex )
{
throw new AutomationException ( string . Format ( "Failed to parse {0} as an FEngineVersion compatible string" , versionString ) , ex ) ;
}
}
public static DateTime BuildTime ( )
{
string VerFile = CommandUtils . CombinePaths ( CommandUtils . CmdEnv . LocalRoot , "Engine" , "Build" , "build.properties" ) ;
var VerLines = CommandUtils . ReadAllText ( VerFile ) ;
var SearchFor = "TimestampForBVT=" ;
int Index = VerLines . IndexOf ( SearchFor ) ;
if ( Index < 0 )
{
throw new AutomationException ( "Could not find {0} in {1} for file {2}" , SearchFor , VerLines , VerFile ) ;
}
Index = Index + SearchFor . Length ;
var Parts = VerLines . Substring ( Index ) . Split ( '.' , '_' , '-' , '\n' , '\r' , '\t' ) ;
DateTime Result = new DateTime ( int . Parse ( Parts [ 0 ] ) , int . Parse ( Parts [ 1 ] ) , int . Parse ( Parts [ 2 ] ) , int . Parse ( Parts [ 3 ] ) , int . Parse ( Parts [ 4 ] ) , int . Parse ( Parts [ 5 ] ) ) ;
CommandUtils . Log ( "Current Build Time is {0}" , Result ) ;
return Result ;
}
}
#endregion
#region VersionFileUpdater
/// <summary>
/// VersionFileUpdater.
/// </summary>
public class VersionFileUpdater
{
/// <summary>
/// Constructor
/// </summary>
public VersionFileUpdater ( string Filename )
{
MyFile = new FileInfo ( Filename ) ;
2014-04-30 07:58:10 -04:00
Lines = new List < string > ( InternalUtils . SafeReadAllLines ( Filename ) ) ;
if ( CommandUtils . IsNullOrEmpty ( Lines ) )
2014-03-14 14:13:41 -04:00
{
throw new AutomationException ( "Version file {0} was empty or not found!" , Filename ) ;
}
}
/// <summary>
/// Doc
/// </summary>
public void ReplaceLine ( string StartOfLine , string ReplacementRHS )
{
2014-04-30 07:58:10 -04:00
for ( int Index = 0 ; Index < Lines . Count ; + + Index )
2014-03-14 14:13:41 -04:00
{
if ( Lines [ Index ] . StartsWith ( StartOfLine ) )
{
Lines [ Index ] = StartOfLine + ReplacementRHS ;
return ;
}
}
throw new AutomationException ( "Unable to find line {0} in {1}" , StartOfLine , MyFile . FullName ) ;
}
2014-04-30 07:58:10 -04:00
/// <summary>
/// Doc
/// </summary>
public void ReplaceOrAddLine ( string StartOfLine , string ReplacementRHS )
{
if ( Contains ( StartOfLine ) )
{
ReplaceLine ( StartOfLine , ReplacementRHS ) ;
}
else
{
AddLine ( "" ) ;
AddLine ( StartOfLine + ReplacementRHS ) ;
}
}
/// <summary>
/// Adds a new line to the version file
/// </summary>
/// <param name="Line"></param>
public void AddLine ( string Line )
{
Lines . Add ( Line ) ;
}
2014-03-14 14:13:41 -04:00
/// <summary>
/// Doc
/// </summary>
public void SetAssemblyInformationalVersion ( string NewInformationalVersion )
{
// This searches for the AssemblyInformationalVersion string. Most the mess is to allow whitespace in places that are possible.
// Captures the string into a group called "Ver" for replacement.
var regex = new Regex ( @"\[assembly:\s+AssemblyInformationalVersion\s*\(\s*""(?<Ver>.*)""\s*\)\s*]" , RegexOptions . IgnoreCase | RegexOptions . ExplicitCapture ) ;
2014-04-30 07:58:10 -04:00
foreach ( var Index in Enumerable . Range ( 0 , Lines . Count ) )
2014-03-14 14:13:41 -04:00
{
var line = Lines [ Index ] ;
var match = regex . Match ( line ) ;
if ( match . Success )
{
var verGroup = match . Groups [ "Ver" ] ;
var sb = new StringBuilder ( line ) ;
sb . Remove ( verGroup . Index , verGroup . Length ) ;
sb . Insert ( verGroup . Index , NewInformationalVersion ) ;
Lines [ Index ] = sb . ToString ( ) ;
return ;
}
}
throw new AutomationException ( "Failed to find the AssemblyInformationalVersion attribute in {1}" , MyFile . FullName ) ;
}
/// <summary>
/// Doc
/// </summary>
public void Commit ( )
{
MyFile . IsReadOnly = false ;
2014-04-30 07:58:10 -04:00
if ( ! InternalUtils . SafeWriteAllLines ( MyFile . FullName , Lines . ToArray ( ) ) )
2014-03-14 14:13:41 -04:00
{
throw new AutomationException ( "Unable to update version info in {0}" , MyFile . FullName ) ;
}
}
/// <summary>
/// Checks if the version file contains the specified string.
/// </summary>
/// <param name="Text">String to look for.</param>
/// <returns></returns>
public bool Contains ( string Text , bool CaseSensitive = true )
{
foreach ( var Line in Lines )
{
if ( Line . IndexOf ( Text , CaseSensitive ? StringComparison . InvariantCulture : StringComparison . InvariantCultureIgnoreCase ) > = 0 )
{
return true ;
}
}
return false ;
}
/// <summary>
/// Doc
/// </summary>
protected FileInfo MyFile ;
/// <summary>
/// Doc
/// </summary>
2014-04-30 07:58:10 -04:00
protected List < string > Lines ;
2014-03-14 14:13:41 -04:00
}
#endregion
}