Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/ToolChain/RemoteToolChain.cs
Josh Adams 60b019520c Copying //UE4/Dev-Platform to //UE4/Dev-Main (Source: //UE4/Dev-Platform @ 3295257)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3235199 on 2016/12/14 by Joe.Barnes

	Fix new compile error for missing #define

Change 3235340 on 2016/12/14 by Arciel.Rekman

	Linux: refactor of some PlatformMisc functions.

	- RootDir() removed since it was a no-op.
	- Old EngineDir() implementation removed in favor of more generic one that should handle foreign engine dir.
	- Change by CengizT,

Change 3237014 on 2016/12/15 by Michael.Trepka

	Fixed a crash in FChunkCacheWorker constructor

Change 3238305 on 2016/12/16 by Josh.Adams

	- Added a None option to the FKey customization, unless the FKey property had NoClear on it

Change 3240823 on 2016/12/20 by Josh.Stoddard

	Device profiles don't work for iPadPro 9.7 and 12.9

	#jira UE-39943

Change 3241103 on 2016/12/20 by Alicia.Cano

	Android support from Visual Studio
	#jira UEPLAT-1421
	#android

Change 3241357 on 2016/12/20 by Chris.Babcock

	Add gameActivityOnNewIntentAddtions section to Android UPL
	#jira UE-38986
	#PR #2969
	#ue4
	#android

Change 3241941 on 2016/12/21 by Alicia.Cano

	Build Fix

Change 3249832 on 2017/01/06 by Nick.Shin

	refetch on timed out GET/POST requests

	#jira UE-39992  Quicklaunch UFE HTML5 fails with "NS_ERROR_Failure"

Change 3249837 on 2017/01/06 by Nick.Shin

	black box issues fixed:

	use device pixel ratio during width and height checks
	and use canvas dimensions if in full screen -- otherwise store SDL_window dimensions for future use

	#jira UE-36341  HTML5 - View is incorrectly drawn
	#jira UE-32311  Templates on Firefox/Chrome on HTML5 are not full screen during Launch On

Change 3249988 on 2017/01/06 by Josh.Adams

	- Disable the HeartBeat() function on platforms that don't actually want to use the HeartbeatThread
	#jira UE-40305, UE-39291, UE-40113

Change 3253720 on 2017/01/11 by Josh.Adams

	- Added support for a config class to use a specific platform's config hierarchy, so that the editor can read NDA'd platform default settings without needing the settings to in Base*.ini
	  - See SwitchRuntimeSettings.h / GetConfigOverridePlatform()
	- Addiontally made it so that NDAd platforms are saved to Project/Platform/Platform*.ini, instead of Project/Default*.ini (to keep samples .ini files free of NDAd platform settings).
	  - See UObject::GetDefaultConfigFilename()
	- Updated some minor ShooterGame switch settings while cleaning this up

Change 3254162 on 2017/01/11 by Daniel.Lamb

	Avoid trying to load empty package names.
	Fixed issue with iterative ini files being unparseable if they inlcude a colon in them.
	#jira UE-40257, UE-35001
	#test Cook QAGame

Change 3255309 on 2017/01/12 by Daniel.Lamb

	In the derived datacache commandlet wait for texture building to finish before we GC.
	#test DDC QAGame

Change 3255311 on 2017/01/12 by Daniel.Lamb

	Removed debug logging for shader compilation.
	Issue hasn't occured in a while and the logging is annoying.
	#test Cook QAGame

Change 3257024 on 2017/01/13 by Josh.Adams

	- Reread in the target RHIs array every time the list of shader types is needed, instead of caching, because the user could change the settings in the editor, then click cook.
	#jira UE-38691

Change 3259636 on 2017/01/16 by Josh.Adams

	- Fixed split screen render issue with payer 2 getting no geometry
	#jira UE-40684

Change 3260159 on 2017/01/17 by Ben.Marsh

	Added extra logging when deleting a directory fails during ReconcileWorkspace.

Change 3260300 on 2017/01/17 by Ben.Marsh

	More logging for cleaning workspaces.

Change 3261056 on 2017/01/17 by Daniel.Lamb

	Cook on the fly builds now resolve string asset references.
	#test Trivial

Change 3262803 on 2017/01/18 by Joe.Graf

	Added missing support for compiling plugins external to Engine/Plugins & Game/Plugins

Change 3262852 on 2017/01/18 by Joe.Graf

	Fixed the bad robomerge
	Don't try to regenerate projects when adding a content only plugin to a content only project

Change 3264930 on 2017/01/19 by Joe.Barnes

	#include some header files needed when building UFE.

Change 3265728 on 2017/01/20 by Will.Fissler

	PlatformShowcase - Added TestBed_MobileFeatures .umap and related test content.

Change 3267188 on 2017/01/21 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)

Change 3267439 on 2017/01/22 by Arciel.Rekman

	Fix Dev-Platform build.

	- Fixed just to have it compile; perhaps a proper fix is needed.
	- Seems to be caused by CL 3265587 (delegate was changed to return an array of search results instead of a single one).

Change 3267556 on 2017/01/23 by Arciel.Rekman

	Linux: fix MoveFile to work across file systems.

	- PR #3141 with slight changes.

Change 3267843 on 2017/01/23 by Arciel.Rekman

	Remove name collision (macro vs libc++).

	- Redoing CL 3259310.

Change 3267850 on 2017/01/23 by Arciel.Rekman

	Fix wrong always true condition.

	- PLATFORM_LINUX is always defined, but can be 0.

Change 3268048 on 2017/01/23 by Daniel.Lamb

	Integrated fix for rebuild lighting commandlet from Greg Korman @ Impulse Gear.
	#test Rebuild lighting Paragon

Change 3268403 on 2017/01/23 by Josh.Adams

	#BUILDUPGRADENOTES
	- Moved XboxOne and PS4 settings into platform specific .ini files (after using GetConfigOverridePlatform() in their class delcarations)
	- Licensee games that have PS4, XboxOne, Switch settings in DefaultEngine.ini will have those settings saved in the platform version next time the project settings are edited. DOCUMENT THIS!

Change 3272441 on 2017/01/25 by Chris.Babcock

	Fixed documentation error in UnrealPluginLanguage
	#ue4
	#android

Change 3272478 on 2017/01/25 by Chris.Babcock

	Fix another documentation error in UnrealPluginLanguage
	#ue4

Change 3272826 on 2017/01/25 by Chris.Babcock

	Google Cloud Messaging plugin for Android
	#jira UEPLAT-1458
	#ue4
	#android

Change 3272839 on 2017/01/25 by Chris.Babcock

	Fix name of Google Cloud Messaging Sender ID
	#ue4
	#android

Change 3273837 on 2017/01/26 by Daniel.Lamb

	Added check to ensure editor never saves source texture data which has had ReleaseSourceMemory called on it.
	Instead crash as this is a loss of content situation.
	#test Cook paragon cook qagame

Change 3274122 on 2017/01/26 by Alicia.Cano

	Runtime permissions support on Android
	- Removing certain permissions
	#jira UE-38512
	#android

Change 3274311 on 2017/01/26 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)

Change 3274794 on 2017/01/27 by Arciel.Rekman

	Linux: fix installed SDK check (UE-40392).

	- Pull request #3111 by rubu.

Change 3274803 on 2017/01/27 by Arciel.Rekman

	Linux: added few more exceptions to .gitignore (UE-39612).

	- Pull request #3026 by ardneran.

Change 3276247 on 2017/01/27 by Nick.Shin

	HTML5 HeapSize settings - make use of it from UE4 Editor:Platforms:HTML5:Memory:HeapSize

	note: emscripten says this is really no longer needed when using [ -s ALLOW_MEMORY_GROWTH=1 ] -- but tests have shown when using that, the game load/compile times takes longer

	#jira UE-34753  Zen Garden cannot compile in HTML5
	#jira UE-40815  Launching QAGame for HTML5 creates an 'uncaught exception: out of memory'.

Change 3276347 on 2017/01/27 by dan.reynolds

	Android Streaming Test Content

Change 3276682 on 2017/01/29 by Nick.Shin

	HTML5 thirdparty build scripts

	- fix up what looks like a bad merge
	- allow linux to also build these  libs
	- fixed harfbuzz to use freetype2-2.6 when building HTML5 libs

	- tested on mac, linux, and windows (git-bash)

Change 3276796 on 2017/01/29 by Nick.Shin

	HTML5 thirdparty (python) build scripts

	- linux patches from mozilla's jukka

	- tested on mac and, linux, and windows (git-bash)

	part of:
	#jira UEPLAT-1437  (4.16)  Switch [to] web assembly

Change 3276803 on 2017/01/29 by Nick.Shin

	HTML5 thirdparty build scripts

	- getting ready to build with (new toolchain that has) wasm support
	- minor fix to handle whitespace in project path

	- tested on mac and, linux, and windows (git-bash)

	part of:
	#jira UEPLAT-1437  (4.16)  Switch [to] web assembly

Change 3278007 on 2017/01/30 by Arciel.Rekman

	SteamVR: whitelist for Linux.

	- Makes Blueprint functions available in Linux builds, even if stubbed.
	- Can be probably whitelisted for Mac too.

Change 3278172 on 2017/01/30 by Arciel.Rekman

	Do not rebuild UnrealPak locally (UE-41285).

Change 3279873 on 2017/01/31 by Brent.Pease

	 + Implement streaming in Vorbis
	 + Add streaming to Android audio
	 + Fix audio streaming chunk race condition

Change 3280063 on 2017/01/31 by Brent.Pease

	GitHub 2949 : Fix for crashes when backgrounding/sleeping on iOS metal devices

	#2949
	#jira UE-38829

Change 3280072 on 2017/01/31 by Brent.Pease

	PR #2889: Add -distribution when iOS distribution Packaging. with IPhonePackage.exe (Contributed by sangpan)
	https://github.com/EpicGames/UnrealEngine/pull/2889

	#jira ue-37874
	#2889

Change 3280091 on 2017/01/31 by Arciel.Rekman

	Linux: fix "unable to make writable" toast (UE-37228).

	- Also fixed other platforms that returned inverted the error result.

Change 3280624 on 2017/01/31 by Brent.Pease

	PR #2891: iOS IDFV string allocation fix (Contributed by robertfsegal)
	https://github.com/EpicGames/UnrealEngine/pull/2891

	#2891
	#jira ue-37891

Change 3280625 on 2017/01/31 by Brent.Pease

	GitHub 2576 - Fix UIImagePickerController crash

	#2576
	#jira UE-328888

Change 3281618 on 2017/02/01 by Josh.Adams

	- Fixed hopeful compile error with missing inlcude
	#jira UE-41415

Change 3282277 on 2017/02/01 by Josh.Adams

	- Support 0.12.16 and 1.1.1 (the first versions that can share Oasis)

Change 3282441 on 2017/02/01 by Arciel.Rekman

	Fix Linux editor splash screen (UE-28123).

Change 3282580 on 2017/02/01 by Nick.Shin

	HTML5 - fix "firefox nighly" issue with:

	failed to compile wasm module: CompileError: at offset XXX: initial memory size too big:

	WARNING: this greatly impacts (in browser) compile times

Change 3285991 on 2017/02/03 by Chris.Babcock

	Fix executable path for stripping Android debug symbols (handle non-Windows properly)
	#jira UE-41238
	#ue4
	#android

Change 3286406 on 2017/02/03 by Chris.Babcock

	Save and restore texture filtering for movie playback in all cases
	#jira UE-41565
	#ue4
	#android

Change 3286800 on 2017/02/04 by Chris.Babcock

	Fix executable path for stripping Android debug symbols (handle non-Windows properly)
	#jira UE-41238
	#ue4
	#android

Change 3288598 on 2017/02/06 by Arciel.Rekman

	CodeLite fixes.

	- Use *-Linux-Debug binary for Debug configuration.
	- Fix virtual paths.

Change 3288864 on 2017/02/06 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)
	- Note, Switch is known to not boot with this, fix coming next

Change 3289364 on 2017/02/06 by Josh.Adams

	[BUILDUPGRADENOTES] - Fixed the "type" of the desktop device profiles to be Windows, not WindowsNoEditor, etc. It should be the platform, not a random string.
	- Updated how DeviceProfiles are loaded, especially in the editor, so that we can have NDAd platforms have their default DP values in platform-hidden files
	- This makes use of the ability for a class to override the platform hierarchy in the editor (like we do with other editor-exposed platform objects)
	- Added Config/[PS4|XboxOne|Switch]/ConfidentialPlatform.ini files so that the DP loading code knows to look in their directories for DPs. See FGenericPlatformMisc::GetConfidentialPlatforms() for more information
	- Note that saving still saves the entire DP to the .ini. Next DP change is to have them properly save against their 2(!) parents - the .ini file earlier in the hierarchy, and the parent DP object. Makes it tricky, for sure.
	- Added FConfigFile::GetArray (previous was only on FConfigCacheIni)

Change 3289796 on 2017/02/07 by Arciel.Rekman

	Linux: remove leftover CEF build script.

Change 3289872 on 2017/02/07 by Arciel.Rekman

	Linux: install MIME types (UE-40954).

	- Pull request #3154 by RicardoEPRodrigues.

Change 3289915 on 2017/02/07 by Josh.Adams

	- Fixed CIS warnings

Change 3289916 on 2017/02/07 by Arciel.Rekman

	Linux: remove -opengl4 from the default invocation.

Change 3290009 on 2017/02/07 by Gil.Gribb

	UE4 - Fixed boot time EDL causing some issues even when it wasn't being used.

Change 3290120 on 2017/02/07 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)

Change 3290948 on 2017/02/07 by Arciel.Rekman

	Linux: fix crash when clicking on question mark (UE-41634).

	- Symbol interposition problem (proper fix is still to be investigated).

	(Edigrating part of CL 3290683 from Release-4.15 to Dev-Platform)

Change 3291074 on 2017/02/07 by Arciel.Rekman

	Speculative build fix.

Change 3292028 on 2017/02/08 by Josh.Adams

	- Fixed Incremental CIS build failures

Change 3292105 on 2017/02/08 by Nick.Shin

	emcc.py - change warning to info

	#jira UE-41747 //UE4/Dev-Platform Compile UE4Game HTML5 completed with 50 warnings

Change 3292201 on 2017/02/08 by JohnHenry.Carawon

	Change comment to fix XML warning when generating project files on Linux

Change 3292242 on 2017/02/08 by Arciel.Rekman

	Linux: avoid unnecessary dependency on CEF (UE-41634).

	- Do not apply CEF workaround to monolithic builds (eg. stock Game/Server targets).
	- Also disable CEF compilation for ShaderCompileWorker.
	- Based on CL 3292077 in 4.15.

Change 3292559 on 2017/02/08 by Josh.Adams

	- Added more platforms to disable the file handle caching (all the ones that use MANAGED_FILE_HANDLES)

Change 3294333 on 2017/02/09 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)

Change 3294506 on 2017/02/09 by Josh.Adams

	- Fixed GoogleCloudMessaging.uplugin to fix the Installed flag. Every other plugin had false, this one had true, which caused various checks to go haywire
	#jira UE-41710

Change 3294984 on 2017/02/09 by Josh.Adams

	- Worked around the remote compiling issue with code-based projects on a different drive than the engine
	#jira UE-41704

Change 3295056 on 2017/02/09 by Josh.Adams

	- Fixed the remote compiling issue by unconverting the path back to host when reading from the module filename

Change 3295161 on 2017/02/09 by Josh.Adams

	- Fixed new bug when buildin native ios that was caused by a remote compile break

Change 3295229 on 2017/02/09 by Josh.Adams

	- Fixed a crash in clothing on platforms that don't support clothing
	#jira UE-41830

[CL 3295859 by Josh Adams in Main branch]
2017-02-09 19:20:55 -05:00

1161 lines
40 KiB
C#

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Linq;
using System.Security.AccessControl;
using System.Xml;
using System.Text;
using System.Text.RegularExpressions;
namespace UnrealBuildTool
{
abstract class RemoteToolChain : UEToolChain
{
/// <summary>
/// Common error codes reported by Remote Tool Chain and its actions.
/// </summary>
public enum RemoteToolChainErrorCode
{
NoError = 0,
ServerNameNotSpecified = 1,
ServerNotResponding = 2,
MissingDeltaCopyInstall = 3,
MissingRemoteUserName = 4,
MissingSSHKey = 5,
SSHCommandFailed = 6,
};
protected readonly FileReference ProjectFile;
public RemoteToolChain(CppPlatform InCppPlatform, UnrealTargetPlatform InRemoteToolChainPlatform, FileReference InProjectFile)
: base(InCppPlatform)
{
RemoteToolChainPlatform = InRemoteToolChainPlatform;
ProjectFile = InProjectFile;
}
/// <summary>
/// These two variables will be loaded from XML config file in XmlConfigLoader.Init()
/// </summary>
[XmlConfigFile]
public static string RemoteServerName = "";
[XmlConfigFile]
public static string[] PotentialServerNames = new string[] { };
/// <summary>
/// Save the specified port so that RemoteServerName is the machine address only
/// </summary>
private static int RemoteServerPort = 22; // Default ssh port
/// <summary>
/// Keep a list of remote files that are potentially copied from local to remote
/// </summary>
private static Dictionary<FileItem, FileItem> CachedRemoteFileItems = new Dictionary<FileItem, FileItem>();
/// <summary>
/// The base path (on the Mac) to the your particular development directory, where files will be copied to from the PC
/// </summary>
public static string UserDevRootMacBase = "/UE4/Builds/";
/// <summary>
/// The final path (on the Mac) to your particular development directory, where files will be copied to from the PC
/// </summary>
public static string UserDevRootMac = "/UE4/Builds";
/// <summary>
/// Whether or not to connect to UnrealRemoteTool using RPCUtility
/// </summary>
[XmlConfigFile]
public static bool bUseRPCUtil = true;
/// <summary>
/// The user has specified a deltacopy install path
/// </summary>
private static string OverrideDeltaCopyInstallPath = null;
/// <summary>
/// Path to rsync executable and parameters for your rsync utility
/// </summary>
[XmlConfigFile]
public static string RSyncExe = "${ENGINE_ROOT}\\Engine\\Extras\\ThirdPartyNotUE\\DeltaCopy\\Binaries\\rsync.exe";
public static string ResolvedRSyncExe = null;
/// <summary>
/// Path to rsync executable and parameters for your rsync utility
/// </summary>
[XmlConfigFile]
public static string SSHExe = "${ENGINE_ROOT}\\Engine\\Extras\\ThirdPartyNotUE\\DeltaCopy\\Binaries\\ssh.exe";
public static string ResolvedSSHExe = null;
/// <summary>
/// Instead of looking for RemoteToolChainPrivate.key in the usual places (Documents/Unreal Engine/UnrealBuildTool/SSHKeys, Engine/Build/SSHKeys), this private key will be used if set
/// </summary>
[XmlConfigFile]
public static string SSHPrivateKeyOverridePath = "";
public static string ResolvedSSHPrivateKey = null;
/// <summary>
/// The authentication used for Rsync (for the -e rsync flag)
/// </summary>
[XmlConfigFile]
public static string RsyncAuthentication = "ssh -i '${CYGWIN_SSH_PRIVATE_KEY}'";
public static string ResolvedRsyncAuthentication = null;
/// <summary>
/// The authentication used for SSH (probably similar to RsyncAuthentication)
/// </summary>
[XmlConfigFile]
public static string SSHAuthentication = "-i '${CYGWIN_SSH_PRIVATE_KEY}'";
public static string ResolvedSSHAuthentication = null;
/// <summary>
/// Username on the remote machine to connect to with RSync
/// </summary>
[XmlConfigFile]
public static string RSyncUsername = "${CURRENT_USER}";
public static string ResolvedRSyncUsername = null;
// has the toolchain initialized remote execution yet? no need to do it multiple times
private static bool bHasBeenInitialized = false;
/// <summary>
/// The directory that this local branch is in, without drive information (strip off X:\ from X:\UE4\iOS)
/// </summary>
public static string BranchDirectory = Path.GetFullPath(".\\");
/// <summary>
/// Substrings that indicate a line contains an error
/// </summary>
protected static List<string> ErrorMessageTokens;
/// <summary>
/// The platform this toolchain is compiling for
/// </summary>
protected UnrealTargetPlatform RemoteToolChainPlatform;
/// <summary>
/// The average amound of memory a compile takes, used so that we don't compile too many things at once
/// </summary>
public static int MemoryPerCompileMB = 1000;
static RemoteToolChain()
{
ErrorMessageTokens = new List<string>();
ErrorMessageTokens.Add("ERROR ");
ErrorMessageTokens.Add("** BUILD FAILED **");
ErrorMessageTokens.Add("[BEROR]");
ErrorMessageTokens.Add("IPP ERROR");
ErrorMessageTokens.Add("System.Net.Sockets.SocketException");
BranchDirectory = BranchDirectory.Replace("Engine\\Binaries\\DotNET", "");
BranchDirectory = BranchDirectory.Replace("Engine\\Source\\", "");
}
private string ResolveString(string Input, bool bIsPath)
{
string Result = Input;
// these assume entire string is a path, and will do file operations on the whole string
if (bIsPath)
{
if (Result.Contains("${PROGRAM_FILES}"))
{
// first look in real ProgramFiles
string Temp = Result.Replace("${PROGRAM_FILES}", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles, Environment.SpecialFolderOption.DoNotVerify));
if (File.Exists(Temp) || Directory.Exists(Temp))
{
Result = Temp;
}
else
{
// fallback to ProgramFilesX86
Temp = Result.Replace("${PROGRAM_FILES}", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86, Environment.SpecialFolderOption.DoNotVerify));
if (File.Exists(Temp) || Directory.Exists(Temp))
{
Result = Temp;
}
}
}
if (Result.Contains("${ENGINE_ROOT}"))
{
string Temp = Result.Replace("${ENGINE_ROOT}", UnrealBuildTool.RootDirectory.FullName);
// get the best version
Result = LookForSpecialFile(Temp);
}
if (Result.Contains("${PROJECT_ROOT}"))
{
if (ProjectFile == null)
{
throw new BuildException("Configuration setting was using ${PROJECT_ROOT}, but there was no project specified");
}
string Temp = Result.Replace("${PROJECT_ROOT}", ProjectFile.Directory.FullName);
// get the best version
Result = LookForSpecialFile(Temp);
}
}
// non path variables
Result = Result.Replace("${CURRENT_USER}", Environment.UserName);
// needs a resolved key (which isn't required if user is using alternate authentication)
if (Result.Contains("${SSH_PRIVATE_KEY}") || Result.Contains("${CYGWIN_SSH_PRIVATE_KEY}"))
{
// if it needs the key, then make sure we have it!
if (ResolvedSSHPrivateKey != null)
{
Result = Result.Replace("${SSH_PRIVATE_KEY}", ResolvedSSHPrivateKey);
Result = Result.Replace("${CYGWIN_SSH_PRIVATE_KEY}", ConvertPathToCygwin(ResolvedSSHPrivateKey));
}
else
{
Result = null;
}
}
return Result;
}
private static string LookForSpecialFile(string InPath)
{
// look in special NotForLicensees dir first
string Special = Path.Combine(Path.GetDirectoryName(InPath), "NoRedist", Path.GetFileName(InPath));
if (File.Exists(Special) || Directory.Exists(Special))
{
return Special;
}
Special = Path.Combine(Path.GetDirectoryName(InPath), "NotForLicensees", Path.GetFileName(InPath));
if (File.Exists(Special) || Directory.Exists(Special))
{
return Special;
}
return InPath;
}
// Look for any build options in the engine config file.
public override void ParseProjectSettings()
{
base.ParseProjectSettings();
DirectoryReference EngineIniPath = ProjectFile != null ? ProjectFile.Directory : null;
if (EngineIniPath == null && UnrealBuildTool.GetRemoteIniPath() != null)
{
EngineIniPath = new DirectoryReference(UnrealBuildTool.GetRemoteIniPath());
}
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, EngineIniPath, UnrealTargetPlatform.IOS);
string ServerName = RemoteServerName;
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "RemoteServerName", out ServerName) && !String.IsNullOrEmpty(ServerName))
{
RemoteServerName = ServerName;
}
bool bUseRSync = false;
if (Ini.GetBool("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bUseRSync", out bUseRSync))
{
bUseRPCUtil = !bUseRSync;
string UserName = RSyncUsername;
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "RSyncUsername", out UserName) && !String.IsNullOrEmpty(UserName))
{
RSyncUsername = UserName;
}
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "DeltaCopyInstallPath", out OverrideDeltaCopyInstallPath))
{
if (!string.IsNullOrEmpty(OverrideDeltaCopyInstallPath))
{
SSHExe = Path.Combine(OverrideDeltaCopyInstallPath, Path.GetFileName(SSHExe));
RSyncExe = Path.Combine(OverrideDeltaCopyInstallPath, Path.GetFileName(RSyncExe));
}
}
string ConfigKeyPath;
if (Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "SSHPrivateKeyOverridePath", out ConfigKeyPath))
{
if (File.Exists(ConfigKeyPath))
{
SSHPrivateKeyOverridePath = ConfigKeyPath;
}
}
}
}
// Gather a users root path from the remote server. Should only be called once.
public static void SetUserDevRootFromServer()
{
if (!bUseRPCUtil && BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// Only set relative to the users root when using rsync, for now
Hashtable Results = RPCUtilHelper.Command("/", "echo $HOME", null);
if (Results == null)
{
Log.TraceInformation("UserDevRoot Command failed to execute!");
}
else if (Results["CommandOutput"] != null)
{
// pass back the string
string HomeLocation = Results["CommandOutput"] as string;
UserDevRootMac = HomeLocation + UserDevRootMacBase;
}
}
else
{
UserDevRootMac = UserDevRootMacBase;
}
}
// Do any one-time, global initialization for the tool chain
static RemoteToolChainErrorCode InitializationErrorCode = RemoteToolChainErrorCode.NoError;
private RemoteToolChainErrorCode InitializeRemoteExecution(bool bFlushBuildDir)
{
if (bHasBeenInitialized)
{
return InitializationErrorCode;
}
// don't need to set up the remote environment if we're simply listing build folders.
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// If we don't care which machine we're going to build on, query and
// pick the one with the most free command slots available
if (RemoteServerName == "best_available")
{
int AvailableSlots = 0;
int Attempts = 0;
if (!ProjectFileGenerator.bGenerateProjectFiles)
{
Log.TraceInformation("Picking a random Mac builder...");
}
while (AvailableSlots < 2 && Attempts < 20)
{
RemoteServerName = PotentialServerNames.OrderBy(x => Guid.NewGuid()).FirstOrDefault();
// make sure it's ready to take commands
AvailableSlots = GetAvailableCommandSlotCount(RemoteServerName);
Attempts++;
}
// make sure it succeeded
if (AvailableSlots <= 1)
{
throw new BuildException("Failed to find a Mac available to take commands!");
}
else if (!ProjectFileGenerator.bGenerateProjectFiles)
{
Log.TraceInformation("Chose {0} after {1} attempts to find a Mac, with {2} slots", RemoteServerName, Attempts, AvailableSlots);
}
/*
* this does not work right, because it pushes a lot of tasks to machines that have substantially more slots than others
Log.TraceInformation("Picking the best available Mac builder...");
Int32 MostAvailableCount = Int32.MinValue;
foreach (string NextMacName in PotentialServerNames)
{
Int32 NextAvailableCount = GetAvailableCommandSlotCount(NextMacName);
if (NextAvailableCount > MostAvailableCount)
{
MostAvailableName = NextMacName;
MostAvailableCount = NextAvailableCount;
}
Log.TraceVerbose("... " + NextMacName + " has " + NextAvailableCount + " slots available");
}
Log.TraceVerbose("Picking the compile server with the most available command slots: " + MostAvailableName);
// Finally, assign the name of the Mac we're going to use
RemoteServerName = MostAvailableName;
*/
}
else if (!ProjectFileGenerator.bGenerateProjectFiles)
{
Log.TraceInformation("Picking the default remote server " + RemoteServerName);
}
// we need a server name!
if (string.IsNullOrEmpty(RemoteServerName))
{
Log.TraceError("Remote compiling requires a server name. Use the editor (Project Settings, IOS) to set up your remote compilation settings.");
return RemoteToolChainErrorCode.ServerNameNotSpecified;
}
// Split port out from RemoteServerName
String[] RemoteServerNameSplit = RemoteServerName.Split(':');
if(RemoteServerNameSplit.Length > 1)
{
if(RemoteServerNameSplit.Length != 2)
{
Log.TraceError("Remote compiling server name contains too many colons.");
return RemoteToolChainErrorCode.ServerNameNotSpecified;
}
RemoteServerName = RemoteServerNameSplit[0];
RemoteServerPort = Convert.ToInt32(RemoteServerNameSplit[1]);
}
if (!bUseRPCUtil)
{
// Verify the Delta Copy install path
ResolvedRSyncExe = ResolveString(RSyncExe, true);
ResolvedSSHExe = ResolveString(SSHExe, true);
if (!File.Exists(ResolvedRSyncExe) || !File.Exists(ResolvedSSHExe))
{
Log.TraceError("Remote compiling requires Delta Copy to be installed. Use the editor (Project Settings, IOS) to set up your remote compilation settings.");
return RemoteToolChainErrorCode.MissingDeltaCopyInstall;
}
// we need the RemoteServerName and the Username to find the private key
ResolvedRSyncUsername = ResolveString(RSyncUsername, false);
if (string.IsNullOrEmpty(ResolvedRSyncUsername))
{
Log.TraceError("Remote compiling requires a user name. Use the editor (Project Settings, IOS) to set up your remote compilation settings.");
return RemoteToolChainErrorCode.MissingRemoteUserName;
}
bool bFoundOverrideSSHPrivateKey = false;
// if the override path is set, just use it directly
if (!string.IsNullOrEmpty(SSHPrivateKeyOverridePath))
{
ResolvedSSHPrivateKey = ResolveString(SSHPrivateKeyOverridePath, true);
bFoundOverrideSSHPrivateKey = File.Exists(ResolvedSSHPrivateKey);
// make sure it exists
if (!bFoundOverrideSSHPrivateKey)
{
Log.TraceWarning("An SSHKey override was specified [" + SSHPrivateKeyOverridePath + "] but it doesn't exist. Looking elsewhere...");
}
}
if (!bFoundOverrideSSHPrivateKey)
{
// all the places to look for a key
List<string> Locations = new List<string>();
Locations.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Unreal Engine", "UnrealBuildTool"));
Locations.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Unreal Engine", "UnrealBuildTool"));
if (ProjectFile != null)
{
Locations.Add(Path.Combine(ProjectFile.Directory.FullName, "Build", "NotForLicensees"));
Locations.Add(Path.Combine(ProjectFile.Directory.FullName, "Build", "NoRedist"));
Locations.Add(Path.Combine(ProjectFile.Directory.FullName, "Build"));
}
Locations.Add(Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Build", "NotForLicensees"));
Locations.Add(Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Build", "NoRedist"));
Locations.Add(Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Build"));
// look for a key file
foreach (string Location in Locations)
{
string KeyPath = Path.Combine(Location, "SSHKeys", RemoteServerName, ResolvedRSyncUsername, "RemoteToolChainPrivate.key");
if (File.Exists(KeyPath))
{
ResolvedSSHPrivateKey = KeyPath;
bFoundOverrideSSHPrivateKey = true;
break;
}
}
}
// resolve the rest of the strings
ResolvedRsyncAuthentication = ResolveString(RsyncAuthentication, false) + " -p " + RemoteServerPort;
ResolvedSSHAuthentication = ResolveString(SSHAuthentication, false) + " -p " + RemoteServerPort;
}
// start up remote communication and record if it succeeds
InitializationErrorCode = (RemoteToolChainErrorCode)RPCUtilHelper.Initialize(RemoteServerName, bFlushBuildDir);
if (InitializationErrorCode != RemoteToolChainErrorCode.NoError && InitializationErrorCode != RemoteToolChainErrorCode.MissingSSHKey)
{
Log.TraceError("Failed to initialize a connection to the Remote Server {0}", RemoteServerName);
return InitializationErrorCode;
}
else if (InitializationErrorCode == RemoteToolChainErrorCode.MissingSSHKey)
{
// Allow the user to set up a key from here.
Process KeyProcess = new Process();
KeyProcess.StartInfo.WorkingDirectory = DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Build", "BatchFiles").FullName;
KeyProcess.StartInfo.FileName = "MakeAndInstallSSHKey.bat";
KeyProcess.StartInfo.Arguments = string.Format(
"\"{0}\" {1} \"{2}\" {3} {4} \"{5}\" \"{6}\" \"{7}\"",
ResolvedSSHExe,
RemoteServerPort,
ResolvedRSyncExe,
ResolvedRSyncUsername,
RemoteServerName,
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
ConvertPathToCygwin(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)),
UnrealBuildTool.EngineDirectory.FullName);
KeyProcess.Start();
KeyProcess.WaitForExit();
// make sure it succeeded if we want to re-init
if (KeyProcess.ExitCode == 0)
{
InitializationErrorCode = InitializeRemoteExecution(bFlushBuildDir);
}
}
}
else
{
RemoteServerName = Environment.MachineName;
// can't error in this case
}
bHasBeenInitialized = true;
return InitializationErrorCode;
}
protected override void AddPrerequisiteSourceFile(CppCompileEnvironment CompileEnvironment, FileItem SourceFile, List<FileItem> PrerequisiteItems)
{
base.AddPrerequisiteSourceFile(CompileEnvironment, SourceFile, PrerequisiteItems);
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) // Don't use remote features when compiling from a Mac
{
QueueFileForBatchUpload(SourceFile);
// @todo ubtmake: What if one of the prerequisite files has become missing since it was updated in our cache? (usually, because a coder eliminated the source file)
// -> Two CASES:
// 1) NOT WORKING: Non-unity file went away (SourceFile in this context). That seems like an existing old use case. Compile params or Response file should have changed?
// 2) WORKING: Indirect file went away (unity'd original source file or include). This would return a file that no longer exists and adds to the prerequiteitems list
List<FileItem> IncludedFileList = CompileEnvironment.Headers.FindAndCacheAllIncludedFiles(SourceFile, CompileEnvironment.IncludePaths, bOnlyCachedDependencies: CompileEnvironment.Headers.bUseUBTMakefiles);
if (IncludedFileList != null)
{
foreach (FileItem IncludedFile in IncludedFileList)
{
QueueFileForBatchUpload(IncludedFile);
}
}
}
}
public override void SetUpGlobalEnvironment(ReadOnlyTargetRules Target)
{
base.SetUpGlobalEnvironment(Target);
// connect to server
if (InitializeRemoteExecution(Target.bFlushBuildDirOnRemoteMac) == RemoteToolChainErrorCode.NoError)
{
// Setup root directory to use.
SetUserDevRootFromServer();
}
}
/// <summary>
/// Converts the passed in path from UBT host to compiler native format.
/// </summary>
public static string ConvertPath(string OriginalPath)
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
if (OriginalPath[1] != ':')
{
if (OriginalPath[0] == '/')
{
return OriginalPath.Replace("\\", "/");
}
throw new BuildException("Can only convert full paths ({0})", OriginalPath);
}
string MacPath = string.Format("{0}{1}/{2}/{3}",
UserDevRootMac,
Environment.MachineName,
OriginalPath[0].ToString().ToUpper(),
OriginalPath.Substring(3));
// clean the path
MacPath = MacPath.Replace("\\", "/");
return MacPath;
}
else
{
return OriginalPath.Replace("\\", "/");
}
}
public static string UnconvertPath(string RemotePath)
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
string StrippedPath = RemotePath.Replace("\\", "/");
// skip over the UserDevRootMac and MachineName
string StuffToSkip = string.Format("{0}{1}", UserDevRootMac, Environment.MachineName);
if (!StrippedPath.StartsWith(StuffToSkip))
{
return RemotePath;
}
StrippedPath = StrippedPath.Substring(StuffToSkip.Length);
// now we make sure the path is /{DriverLetter}/
if (StrippedPath[0] != '/' || StrippedPath[2] != '/')
{
return RemotePath;
}
// convert /{DriveLetter}/ to {DriveLetter}:
char DriveLetter = StrippedPath[1];
// and skip over it
StrippedPath = StrippedPath.Substring(3);
// put back into PC parlance
return string.Format("{0}:/{1}", DriveLetter, StrippedPath).Replace("/", "\\");
}
else
{
return RemotePath;
}
}
protected string GetMacDevSrcRoot()
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// figure out the remote version of Engine/Source
return ConvertPath(Path.GetFullPath(Path.Combine(BranchDirectory, "Engine/Source/")));
}
else
{
return UnrealBuildTool.EngineSourceDirectory.FullName; ;
}
}
private static List<string> RsyncDirs = new List<string>();
private static List<string> RsyncExtensions = new List<string>();
public static void QueueFileForBatchUpload(FileItem LocalFileItem)
{
// Now, we actually just remember unique directories with any files, and upload all files in them to the remote machine
// (either via rsync, or RPCUtil acting like rsync)
string Entry = Path.GetDirectoryName(LocalFileItem.AbsolutePath);
if (!RsyncDirs.Contains(Entry))
{
RsyncDirs.Add(Entry);
}
string Ext = Path.GetExtension(LocalFileItem.AbsolutePath);
if (Ext == "")
{
Ext = Path.GetFileName(LocalFileItem.AbsolutePath);
}
if (!RsyncExtensions.Contains(Ext))
{
RsyncExtensions.Add(Ext);
}
}
public FileItem LocalToRemoteFileItem(FileItem LocalFileItem, bool bShouldUpload)
{
FileItem RemoteFileItem = null;
// Look to see if we've already made a remote FileItem for this local FileItem
if (!CachedRemoteFileItems.TryGetValue(LocalFileItem, out RemoteFileItem))
{
// If not, create it now
string RemoteFilePath = ConvertPath(LocalFileItem.AbsolutePath);
RemoteFileItem = FileItem.GetRemoteItemByPath(RemoteFilePath, RemoteToolChainPlatform);
// Is shadowing requested?
if (bShouldUpload)
{
QueueFileForBatchUpload(LocalFileItem);
}
CachedRemoteFileItems.Add(LocalFileItem, RemoteFileItem);
}
return RemoteFileItem;
}
public FileItem RemoteToLocalFileItem(FileItem RemoteFileItem)
{
// Look to see if we've already made a remote FileItem for this local FileItem
foreach (var Item in CachedRemoteFileItems)
{
if (Item.Value.AbsolutePath == RemoteFileItem.AbsolutePath)
{
return Item.Key;
}
}
return RemoteFileItem;
}
/// <summary>
/// Helper function to sync source files to and from the local system and a remote Mac
/// </summary>
//This chunk looks to be required to pipe output to VS giving information on the status of a remote build.
public static bool OutputReceivedDataEventHandlerEncounteredError = false;
public static string OutputReceivedDataEventHandlerEncounteredErrorMessage = "";
public static void OutputReceivedDataEventHandler(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null))
{
Log.TraceInformation(Line.Data);
foreach (string ErrorToken in ErrorMessageTokens)
{
if (Line.Data.Contains(ErrorToken))
{
OutputReceivedDataEventHandlerEncounteredError = true;
OutputReceivedDataEventHandlerEncounteredErrorMessage += Line.Data;
break;
}
}
}
}
public static void PostCodeGeneration(UHTManifest Manifest)
{
if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
{
// @todo UHT: Temporary workaround for UBT no longer being able to follow includes from generated headers unless
// the headers already existed before the build started. We're working on a proper fix.
// Make sure all generated headers are synced. If we had to generate code, we need to assume that not all of the
// header files existed on disk at the time that UBT scanned include statements looking for prerequisite files. Those
// files are created during code generation and must exist on disk by the time this function is called. We'll scan
// for generated code files and make sure they are enqueued for copying to the remote machine.
foreach (var UObjectModule in Manifest.Modules)
{
// @todo uht: Ideally would only copy exactly the files emitted by UnrealHeaderTool, rather than scanning directory (could copy stale files; not a big deal though)
try
{
var GeneratedCodeDirectory = Path.GetDirectoryName(UObjectModule.GeneratedCPPFilenameBase);
var GeneratedCodeFiles = Directory.GetFiles(GeneratedCodeDirectory, "*", SearchOption.AllDirectories);
foreach (var GeneratedCodeFile in GeneratedCodeFiles)
{
// Skip copying "Timestamp" files (UBT temporary files)
if (!Path.GetFileName(GeneratedCodeFile).Equals(@"Timestamp", StringComparison.InvariantCultureIgnoreCase))
{
var GeneratedCodeFileItem = FileItem.GetExistingItemByPath(GeneratedCodeFile);
QueueFileForBatchUpload(GeneratedCodeFileItem);
}
}
}
catch (System.IO.DirectoryNotFoundException)
{
// Ignore directory not found
}
// For source files in legacy "Classes" directories, we need to make sure they all get copied over too, since
// they may not have been directly included in any C++ source files (only generated headers), and the initial
// header scan wouldn't have picked them up if they hadn't been generated yet!
try
{
var SourceFiles = Directory.GetFiles(UObjectModule.BaseDirectory, "*", SearchOption.AllDirectories);
foreach (var SourceFile in SourceFiles)
{
var SourceFileItem = FileItem.GetExistingItemByPath(SourceFile);
QueueFileForBatchUpload(SourceFileItem);
}
}
catch (System.IO.DirectoryNotFoundException)
{
// Ignore directory not found
}
}
}
}
static public void OutputReceivedForRsync(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null) && (Line.Data != ""))
{
Log.TraceInformation(Line.Data);
}
}
private static Dictionary<Object, StringBuilder> SSHOutputMap = new Dictionary<object, StringBuilder>();
private static System.Threading.Mutex DictionaryLock = new System.Threading.Mutex();
static public void OutputReceivedForSSH(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null) && (Line.Data != ""))
{
DictionaryLock.WaitOne();
StringBuilder SSHOutput = SSHOutputMap[Sender];
DictionaryLock.ReleaseMutex();
if (SSHOutput.Length != 0)
{
SSHOutput.Append(Environment.NewLine);
}
SSHOutput.Append(Line.Data);
}
}
private static string ConvertPathToCygwin(string InPath)
{
if (InPath == null)
{
return null;
}
return "/cygdrive/" + Utils.CleanDirectorySeparators(InPath.Replace(":", ""), '/');
}
public static void PreBuildSync()
{
// no need to sync on the Mac!
if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac)
{
return;
}
if (bUseRPCUtil)
{
string ExtString = "";
// look only for useful extensions
foreach (string Ext in RsyncExtensions)
{
// for later ls
ExtString += Ext.StartsWith(".") ? ("*" + Ext) : Ext;
ExtString += " ";
}
List<string> BatchUploadCommands = new List<string>();
// for each directory we visited, add all the files in that directory
foreach (string Dir in RsyncDirs)
{
List<string> LocalFilenames = new List<string>();
if (!Directory.Exists(Dir))
{
Directory.CreateDirectory(Dir);
}
// look only for useful extensions
foreach (string Ext in RsyncExtensions)
{
string[] Files = Directory.GetFiles(Dir, "*" + Ext);
foreach (string SyncFile in Files)
{
// remember all local files
LocalFilenames.Add(Path.GetFileName(SyncFile));
string RemoteFilePath = ConvertPath(SyncFile);
// an upload command is local name and remote name
BatchUploadCommands.Add(SyncFile + ";" + RemoteFilePath);
}
}
}
// batch upload
RPCUtilHelper.BatchUpload(BatchUploadCommands.ToArray());
}
else
{
List<string> RelativeRsyncDirs = new List<string>();
foreach (string Dir in RsyncDirs)
{
RelativeRsyncDirs.Add(Utils.CleanDirectorySeparators(Dir.Replace(":", ""), '/') + "/");
}
// write out directories to copy
string RSyncPathsFile = Path.GetTempFileName();
string IncludeFromFile = Path.GetTempFileName();
File.WriteAllLines(RSyncPathsFile, RelativeRsyncDirs.ToArray());
File.WriteAllLines(IncludeFromFile, RsyncExtensions);
// source and destination paths in the format rsync wants
string CygRootPath = "/cygdrive";// ConvertPathToCygwin(Path.GetFullPath(""));
string RemotePath = string.Format("{0}{1}",
UserDevRootMac,
Environment.MachineName);
// get the executable dir for SSH, so Rsync can call it easily
string ExeDir = Path.GetDirectoryName(ResolvedSSHExe);
Process RsyncProcess = new Process();
if (ExeDir != "")
{
RsyncProcess.StartInfo.WorkingDirectory = ExeDir;
}
// --exclude='*' ??? why???
RsyncProcess.StartInfo.FileName = ResolvedRSyncExe;
RsyncProcess.StartInfo.Arguments = string.Format(
"-vzae \"{0}\" --rsync-path=\"mkdir -p {2} && rsync\" --chmod=ug=rwX,o=rxX --delete --files-from=\"{4}\" --include-from=\"{5}\" --include='*/' --exclude='*.o' --exclude='Timestamp' '{1}' {6}@{3}:'{2}'",
ResolvedRsyncAuthentication,
CygRootPath,
RemotePath,
RemoteServerName,
ConvertPathToCygwin(RSyncPathsFile),
ConvertPathToCygwin(IncludeFromFile),
RSyncUsername);
Console.WriteLine("Command: " + RsyncProcess.StartInfo.Arguments);
RsyncProcess.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync);
RsyncProcess.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync);
// run rsync
Utils.RunLocalProcess(RsyncProcess);
File.Delete(IncludeFromFile);
File.Delete(RSyncPathsFile);
}
// we can now clear out the set of files
RsyncDirs.Clear();
RsyncExtensions.Clear();
}
static public bool UploadFile(string LocalPath, string RemotePath)
{
string RemoteDir = Path.GetDirectoryName(RemotePath).Replace("\\", "/");
RemoteDir = RemoteDir.Replace(" ", "\\ ");
string RemoteFilename = Path.GetFileName(RemotePath);
// get the executable dir for SSH, so Rsync can call it easily
string ExeDir = Path.GetDirectoryName(ResolvedSSHExe);
Process RsyncProcess = new Process();
if (ExeDir != "")
{
RsyncProcess.StartInfo.WorkingDirectory = ExeDir;
}
// make simple rsync commandline to send a file
RsyncProcess.StartInfo.FileName = ResolvedRSyncExe;
RsyncProcess.StartInfo.Arguments = string.Format(
"-zae \"{0}\" --rsync-path=\"mkdir -p {1} && rsync\" --chmod=ug=rwX,o=rxX '{2}' {3}@{4}:'{1}/{5}'",
ResolvedRsyncAuthentication,
RemoteDir,
ConvertPathToCygwin(LocalPath),
RSyncUsername,
RemoteServerName,
RemoteFilename
);
RsyncProcess.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync);
RsyncProcess.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync);
// run rsync (0 means success)
return Utils.RunLocalProcess(RsyncProcess) == 0;
}
static public bool DownloadFile(string RemotePath, string LocalPath)
{
// get the executable dir for SSH, so Rsync can call it easily
string ExeDir = Path.GetDirectoryName(ResolvedSSHExe);
string RemoteDir = RemotePath.Replace(" ", "\\ ");
Process RsyncProcess = new Process();
if (ExeDir != "")
{
RsyncProcess.StartInfo.WorkingDirectory = ExeDir;
}
// make sure directory exists to download to
Directory.CreateDirectory(Path.GetDirectoryName(LocalPath));
// make simple rsync commandline to send a file
RsyncProcess.StartInfo.FileName = ResolvedRSyncExe;
RsyncProcess.StartInfo.Arguments = string.Format(
"-zae \"{0}\" {2}@{3}:'{4}' \"{1}\"",
ResolvedRsyncAuthentication,
ConvertPathToCygwin(LocalPath),
RSyncUsername,
RemoteServerName,
RemoteDir
);
RsyncProcess.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync);
RsyncProcess.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedForRsync);
//Console.WriteLine("COPY: {0} {1}", RsyncProcess.StartInfo.FileName, RsyncProcess.StartInfo.Arguments);
// run rsync (0 means success)
return Utils.RunLocalProcess(RsyncProcess) == 0;
}
static public Hashtable SSHCommand(string WorkingDirectory, string Command, string RemoteOutputPath)
{
Console.WriteLine("Doing {0}", Command);
// make the commandline for other end
string RemoteCommandline = "cd \"" + WorkingDirectory + "\"";
if (!string.IsNullOrWhiteSpace(RemoteOutputPath))
{
RemoteCommandline += " && mkdir -p \"" + Path.GetDirectoryName(RemoteOutputPath).Replace("\\", "/") + "\"";
}
// get the executable dir for SSH
string ExeDir = Path.GetDirectoryName(ResolvedSSHExe);
Process SSHProcess = new Process();
if (ExeDir != "")
{
SSHProcess.StartInfo.WorkingDirectory = ExeDir;
}
// long commands go as a file
if (Command.Length > 1024)
{
// upload the commandline text file
string CommandLineFile = Path.GetTempFileName();
File.WriteAllText(CommandLineFile, Command);
string RemoteCommandlineDir = "/var/tmp/" + Environment.MachineName;
string RemoteCommandlinePath = RemoteCommandlineDir + "/" + Path.GetFileName(CommandLineFile);
DateTime Now = DateTime.Now;
UploadFile(CommandLineFile, RemoteCommandlinePath);
Console.WriteLine("Upload took {0}", (DateTime.Now - Now).ToString());
// execute the file, not a commandline
RemoteCommandline += string.Format(" && bash < {0} && rm {0}", RemoteCommandlinePath);
}
else
{
RemoteCommandline += " && " + Command;
}
SSHProcess.StartInfo.FileName = ResolvedSSHExe;
SSHProcess.StartInfo.Arguments = string.Format(
"-o BatchMode=yes {0} {1}@{2} \"{3}\"",
// "-o CheckHostIP=no {0} {1}@{2} \"{3}\"",
ResolvedSSHAuthentication,
RSyncUsername,
RemoteServerName,
RemoteCommandline.Replace("\"", "\\\""));
Hashtable Return = new Hashtable();
// add this process to the map
DictionaryLock.WaitOne();
SSHOutputMap[SSHProcess] = new StringBuilder("");
DictionaryLock.ReleaseMutex();
SSHProcess.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedForSSH);
SSHProcess.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedForSSH);
DateTime Start = DateTime.Now;
Int64 ExitCode = Utils.RunLocalProcess(SSHProcess);
Console.WriteLine("Execute took {0}", (DateTime.Now - Start).ToString());
// now we have enough to fill out the HashTable
DictionaryLock.WaitOne();
Return["CommandOutput"] = SSHOutputMap[SSHProcess].ToString();
Return["ExitCode"] = (object)ExitCode;
SSHOutputMap.Remove(SSHProcess);
DictionaryLock.ReleaseMutex();
return Return;
}
static public Int32 GetAvailableCommandSlotCount(string TargetMacName)
{
// ask how many slots are available, and increase by 1 (not sure why)
Int32 RemoteAvailableCommandSlotCount = 1 + QueryRemoteMachine(TargetMacName, "rpc:command_slots_available");
Log.TraceVerbose("Available command slot count for " + TargetMacName + " is " + RemoteAvailableCommandSlotCount.ToString());
return RemoteAvailableCommandSlotCount;
}
/// <summary>
/// Translates clang output warning/error messages into vs-clickable messages
/// </summary>
/// <param name="sender"> Sending object</param>
/// <param name="e"> Event arguments (In this case, the line of string output)</param>
protected void RemoteOutputReceivedEventHandler(object sender, DataReceivedEventArgs e)
{
var Output = e.Data;
if (Output == null)
{
return;
}
if (Utils.IsRunningOnMono)
{
Log.TraceInformation(Output);
}
else
{
// Need to match following for clickable links
string RegexFilePath = @"^(\/[A-Za-z0-9_\-\.]*)+\.(cpp|c|mm|m|hpp|h)";
string RegexLineNumber = @"\:\d+\:\d+\:";
string RegexDescription = @"(\serror:\s|\swarning:\s).*";
// Get Matches
string MatchFilePath = Regex.Match(Output, RegexFilePath).Value.Replace("Engine/Source/../../", "");
string MatchLineNumber = Regex.Match(Output, RegexLineNumber).Value;
string MatchDescription = Regex.Match(Output, RegexDescription).Value;
// If any of the above matches failed, do nothing
if (MatchFilePath.Length == 0 ||
MatchLineNumber.Length == 0 ||
MatchDescription.Length == 0)
{
Log.TraceInformation(Output);
return;
}
// Convert Path
string RegexStrippedPath = @"\/Engine\/.*"; //@"(Engine\/|[A-Za-z0-9_\-\.]*\/).*";
string ConvertedFilePath = Regex.Match(MatchFilePath, RegexStrippedPath).Value;
ConvertedFilePath = Path.GetFullPath("..\\.." + ConvertedFilePath);
// Extract Line + Column Number
string ConvertedLineNumber = Regex.Match(MatchLineNumber, @"\d+").Value;
string ConvertedColumnNumber = Regex.Match(MatchLineNumber, @"(?<=:\d+:)\d+").Value;
// Write output
string ConvertedExpression = " " + ConvertedFilePath + "(" + ConvertedLineNumber + "," + ConvertedColumnNumber + "):" + MatchDescription;
Log.TraceInformation(ConvertedExpression); // To create clickable vs link
// Log.TraceInformation(Output); // To preserve readable output log
}
}
/// <summary>
/// Queries the remote compile server for CPU information
/// and computes the proper ProcessorCountMultiplier.
/// </summary>
static private Int32 QueryResult = 0;
static public void OutputReceivedForQuery(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null) && (Line.Data != ""))
{
Int32 TestValue = 0;
if (Int32.TryParse(Line.Data, out TestValue))
{
QueryResult = TestValue;
}
else
{
Log.TraceVerbose("Info: Unexpected output from remote Mac system info query, skipping");
}
}
}
static public Int32 QueryRemoteMachine(string MachineName, string Command)
{
// we must run the commandline RPCUtility, because we could run this before we have opened up the RemoteRPCUtlity
Process QueryProcess = new Process();
QueryProcess.StartInfo.WorkingDirectory = Path.GetFullPath("..\\Binaries\\DotNET");
QueryProcess.StartInfo.FileName = QueryProcess.StartInfo.WorkingDirectory + "\\RPCUtility.exe";
QueryProcess.StartInfo.Arguments = string.Format("{0} {1} sysctl -n hw.ncpu",
MachineName,
UserDevRootMac);
QueryProcess.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedForQuery);
QueryProcess.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedForQuery);
// Try to launch the query's process, and produce a friendly error message if it fails.
Utils.RunLocalProcess(QueryProcess);
return QueryResult;
}
};
}