// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
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;
using Tools.DotNETCommon;
namespace AutomationTool
{
#region UAT Internal Utils
///
/// AutomationTool internal Utilities.
///
public static class InternalUtils
{
///
/// Gets environment variable value.
///
/// Variable name.
/// Default value to be returned if the variable does not exist.
/// Variable value or the default value if the variable did not exist.
public static string GetEnvironmentVariable(string VarName, string Default, bool bQuiet = false)
{
var Value = Environment.GetEnvironmentVariable(VarName);
if (Value == null)
{
Value = Default;
}
if (!bQuiet)
{
Log.TraceLog("GetEnvironmentVariable {0}={1}", VarName, Value);
}
return Value;
}
///
/// Creates a directory.
/// @todo: this function should not exchange exception context for error codes that can be ignored.
///
/// Directory name.
/// True if the directory was created, false otherwise.
public static bool SafeCreateDirectory(string Path, bool bQuiet = false)
{
if( !bQuiet)
{
Log.TraceLog("SafeCreateDirectory {0}", Path);
}
bool Result = true;
try
{
Result = Directory.CreateDirectory(Path).Exists;
}
catch (Exception)
{
if (Directory.Exists(Path) == false)
{
Result = false;
}
}
return Result;
}
///
/// Deletes a file (will remove read-only flag if necessary).
///
/// Filename
/// if true, then do not print errors, also in quiet mode do not retry
/// True if the file does not exist, false otherwise.
public static bool SafeDeleteFile(string Path, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("SafeDeleteFile {0}", Path);
}
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)
{
Thread.Sleep(1000);
}
} while (Result == false && ++Attempts < MaxAttempts);
if (Result == false && LastException != null)
{
if (bQuiet)
{
Log.TraceLog("Failed to delete file {0} in {1} attempts.", Path, MaxAttempts);
}
else
{
Log.TraceWarning("Failed to delete file {0} in {1} attempts.", Path, MaxAttempts);
Log.TraceWarning(LogUtils.FormatException(LastException));
}
}
return Result;
}
///
/// Recursively deletes a directory and all its files and subdirectories.
///
/// Path to delete.
/// Whether the deletion was succesfull.
private static bool RecursivelyDeleteDirectory(string Path, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("RecursivelyDeleteDirectory {0}", Path);
}
// 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);
}
///
/// Deletes an empty directory.
///
/// Path to the Directory.
/// True if deletion was successful, otherwise false.
public static bool SafeDeleteEmptyDirectory(string Path, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("SafeDeleteEmptyDirectory {0}", Path);
}
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)
{
Log.TraceWarning("Failed to delete directory {0} in {1} attempts.", Path, MaxAttempts);
Log.TraceWarning(LogUtils.FormatException(LastException));
}
return Result;
}
///
/// Deletes a directory and all its contents. Will delete read-only files.
///
/// Directory name.
/// True if the directory no longer exists, false otherwise.
public static bool SafeDeleteDirectory(string Path, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("SafeDeleteDirectory {0}", Path);
}
if (Directory.Exists(Path))
{
return RecursivelyDeleteDirectory(Path, bQuiet);
}
else
{
return true;
}
}
///
/// Renames/moves a file.
///
/// Old name
/// New name
/// True if the operation was successful, false otherwise.
public static bool SafeRenameFile(string OldName, string NewName, bool bQuiet = false)
{
if( !bQuiet )
{
Log.TraceLog("SafeRenameFile {0} {1}", OldName, NewName);
}
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)
{
Log.TraceWarning("Failed to rename {0} to {1}", OldName, NewName);
Log.TraceWarning(LogUtils.FormatException(Ex));
Result = false;
}
}
}
while (Result == false && ++Attempts < MaxAttempts);
return Result;
}
// @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",
"KeyPassword",
};
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));
}
///
/// Copies a file.
///
/// Source name
/// Target name
/// True if the operation was successful, false otherwise.
public static bool SafeCopyFile(string SourceName, string TargetName, bool bQuiet = false, bool bFilterSpecialLinesFromIniFiles = false)
{
if (!bQuiet)
{
Log.TraceLog("SafeCopyFile {0} {1}", SourceName, TargetName);
}
const int MaxAttempts = 10;
int Attempts = 0;
bool Result = true;
do
{
Result = true;
bool Retry = true;
try
{
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);
}
Retry = !File.Exists(TargetName);
if (!Retry)
{
FileInfo SourceInfo = new FileInfo(SourceName);
FileInfo TargetInfo = new FileInfo(TargetName);
if (!bSkipSizeCheck && SourceInfo.Length != TargetInfo.Length)
{
Log.TraceWarning("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.TraceWarning("Date mismatch {0} = {1} to {2} = {3}", SourceName, SourceInfo.LastWriteTimeUtc, TargetName, TargetInfo.LastWriteTimeUtc);
Retry = true;
}
}
}
catch (Exception Ex)
{
Log.TraceWarning("SafeCopyFile Exception was {0}", LogUtils.FormatException(Ex));
Retry = true;
}
if (Retry)
{
if (Attempts + 1 < MaxAttempts)
{
Log.TraceWarning("Failed to copy {0} to {1}, deleting, waiting 10s and retrying.", SourceName, TargetName);
if (File.Exists(TargetName))
{
SafeDeleteFile(TargetName);
}
Thread.Sleep(10000);
}
else
{
Log.TraceWarning("Failed to copy {0} to {1}", SourceName, TargetName);
}
Result = false;
}
}
while (Result == false && ++Attempts < MaxAttempts);
return Result;
}
///
/// Reads all lines from a file.
///
/// Filename
/// An array containing all lines read from the file or null if the file could not be read.
public static string[] SafeReadAllLines(string Filename)
{
Log.TraceLog("SafeReadAllLines {0}", Filename);
string[] Result = null;
try
{
Result = File.ReadAllLines(Filename);
}
catch (Exception Ex)
{
Log.TraceWarning("Failed to load {0}", Filename);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Result;
}
///
/// Reads all text from a file.
///
/// Filename
/// String containing all text read from the file or null if the file could not be read.
public static string SafeReadAllText(string Filename)
{
Log.TraceLog("SafeReadAllLines {0}", Filename);
string Result = null;
try
{
Result = File.ReadAllText(Filename);
}
catch (Exception Ex)
{
Log.TraceWarning("Failed to load {0}", Filename);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Result;
}
///
/// Finds files in the specified path.
///
/// Path
/// Search pattern
/// Whether to search recursively or not.
/// List of all files found (can be empty) or null if the operation failed.
public static string[] FindFiles(string Path, string SearchPattern, bool Recursive, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("FindFiles {0} {1} {2}", Path, SearchPattern, Recursive);
}
// 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 (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Linux)
{
return Directory.GetFiles(Path, SearchPattern, Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}
else
{
List FileNames = new List();
DirectoryInfo DirInfo = new DirectoryInfo(Path);
foreach( FileInfo File in DirInfo.EnumerateFiles(SearchPattern, Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
{
if (File.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
if (!bQuiet)
{
Log.TraceWarning("Ignoring symlink {0}", File.FullName);
}
continue;
}
FileNames.Add(File.FullName);
}
return FileNames.ToArray();
}
}
///
/// Finds directories in the specified path.
///
/// Path
/// Search pattern
/// Whether to search recursively or not.
/// List of all directories found (can be empty) or null if the operation failed.
public static string[] FindDirectories(string Path, string SearchPattern, bool Recursive, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("FindDirectories {0} {1} {2}", Path, SearchPattern, Recursive);
}
return Directory.GetDirectories(Path, SearchPattern, Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}
///
/// Finds files in the specified path.
///
/// Path
/// Search pattern
/// Whether to search recursively or not.
/// List of all files found (can be empty) or null if the operation failed.
public static string[] SafeFindFiles(string Path, string SearchPattern, bool Recursive, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("SafeFindFiles {0} {1} {2}", Path, SearchPattern, Recursive);
}
string[] Files = null;
try
{
Files = FindFiles(Path, SearchPattern, Recursive, bQuiet);
}
catch (Exception Ex)
{
Log.TraceWarning("Unable to Find Files in {0}", Path);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Files;
}
///
/// Finds directories in the specified path.
///
/// Path
/// Search pattern
/// Whether to search recursively or not.
/// List of all files found (can be empty) or null if the operation failed.
public static string[] SafeFindDirectories(string Path, string SearchPattern, bool Recursive, bool bQuiet = false)
{
if (!bQuiet)
{
Log.TraceLog("SafeFindDirectories {0} {1} {2}", Path, SearchPattern, Recursive);
}
string[] Directories = null;
try
{
Directories = FindDirectories(Path, SearchPattern, Recursive, bQuiet);
}
catch (Exception Ex)
{
Log.TraceWarning("Unable to Find Directories in {0}", Path);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Directories;
}
///
/// Checks if a file exists.
///
/// Filename
/// if true, do not print a message
/// True if the file exists, false otherwise.
public static bool SafeFileExists(string Path, bool bQuiet = false)
{
bool Result = false;
try
{
Result = File.Exists(Path);
if (!bQuiet)
{
Log.TraceLog("SafeFileExists {0}={1}", Path, Result);
}
}
catch (Exception Ex)
{
Log.TraceWarning("Unable to check if file {0} exists.", Path);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Result;
}
///
/// Checks if a directory exists.
///
/// Directory
/// if true, no longging
/// True if the directory exists, false otherwise.
public static bool SafeDirectoryExists(string Path, bool bQuiet = false)
{
bool Result = false;
try
{
Result = Directory.Exists(Path);
if (!bQuiet)
{
Log.TraceLog("SafeDirectoryExists {0}={1}", Path, Result);
}
}
catch (Exception Ex)
{
Log.TraceWarning("Unable to check if directory {0} exists.", Path);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Result;
}
///
/// Writes lines to a file.
///
/// Filename
/// Text
/// True if the operation was successful, false otherwise.
public static bool SafeWriteAllLines(string Path, string[] Text)
{
Log.TraceLog("SafeWriteAllLines {0}", Path);
bool Result = false;
try
{
File.WriteAllLines(Path, Text);
Result = true;
}
catch (Exception Ex)
{
Log.TraceWarning("Unable to write text to {0}", Path);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Result;
}
///
/// Writes text to a file.
///
/// Filename
/// Text
/// True if the operation was successful, false otherwise.
public static bool SafeWriteAllText(string Path, string Text)
{
Log.TraceLog("SafeWriteAllText {0}", Path);
bool Result = false;
try
{
File.WriteAllText(Path, Text);
Result = true;
}
catch (Exception Ex)
{
Log.TraceWarning("Unable to write text to {0}", Path);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Result;
}
///
/// Writes text to a file.
///
/// Filename
/// Text
/// True if the operation was successful, false otherwise.
public static bool SafeWriteAllBytes(string Path, byte[] Bytes)
{
Log.TraceLog("SafeWriteAllBytes {0}", Path);
bool Result = false;
try
{
File.WriteAllBytes(Path, Bytes);
Result = true;
}
catch (Exception Ex)
{
Log.TraceWarning("Unable to write text to {0}", Path);
Log.TraceWarning(LogUtils.FormatException(Ex));
}
return Result;
}
///
/// Runs the specified delegate checking if this is the only instance of the application.
///
///
///
public static ExitCode RunSingleInstance(Func