You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
294 lines
9.7 KiB
C#
294 lines
9.7 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using EpicGames.Core;
|
|
using AutomationTool;
|
|
using System.Text.RegularExpressions;
|
|
using System.IO;
|
|
|
|
namespace Turnkey
|
|
{
|
|
class PerforceCopyProvider : CopyProvider
|
|
{
|
|
public override string ProviderToken { get { return "perforce"; } }
|
|
|
|
|
|
public override string Execute(string Operation, CopyExecuteSpecialMode SpecialMode, string SpecialModeHint)
|
|
{
|
|
if (!PrepareForOperation(Operation))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// calculate the output path
|
|
// @todo turnkey: use p4 where, because a depot mapping could be strange and make this invlalid
|
|
string OutputPath = Operation.Replace(ConnectedRoot, PerforceClient.RootPath);
|
|
|
|
// turn //a/b/c/foo*/... to //a/b/c
|
|
OutputPath = GetDirectoryBeforeWildcard(OutputPath);
|
|
|
|
OutputPath = OutputPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|
|
|
TurnkeyUtils.Log("Syncing '{0}' to '{1}'", Operation, OutputPath);
|
|
|
|
// now do the actual sync
|
|
PerforceConnection.Sync(Operation, AllowSpew: false);
|
|
|
|
return OutputPath;
|
|
}
|
|
|
|
public override string[] Enumerate(string Operation, List<List<string>> Expansions)
|
|
{
|
|
// if we have no wildcards, there's no need to waste time touching p4, just return the spec
|
|
if (!Operation.Contains("*") && !Operation.Contains("..."))
|
|
{
|
|
return new string[] { ProviderToken + ":" + Operation };
|
|
}
|
|
|
|
// connect to the stream
|
|
if (!PrepareForOperation(Operation))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// now get the file list that matches
|
|
List<string> Results = PerforceConnection.Files(Operation);
|
|
|
|
if (Expansions != null)
|
|
{
|
|
// figure out what the *'s turned into (note we use case-insensitive because p4 has an annoying habit of being case-preserving of directories
|
|
// on a per-file basis (//depot/UE4/Foo/* might return //depot/UE4/Foo/A and //Depot/ue4/FOO/B)
|
|
Regex Expression = new Regex(Operation.Replace("/", "\\/").Replace("*", "([^\\/]*)").Replace("...", ".*"), RegexOptions.IgnoreCase);
|
|
|
|
// run the regex on each result
|
|
foreach (string Result in Results)
|
|
{
|
|
List<string> ResultExpansions = new List<string>();
|
|
Expansions.Add(ResultExpansions);
|
|
|
|
Match ResultMatch = Expression.Match(Result);
|
|
for (int Index = 1; Index < ResultMatch.Groups.Count; Index++)
|
|
{
|
|
ResultExpansions.Add(ResultMatch.Groups[Index].Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Results.Select(x => ProviderToken + ":" + x).ToArray();
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetDirectoryBeforeWildcard(string Input)
|
|
{
|
|
// find the first wildcard and that's the lowest down outputpath we can use
|
|
int DotsLocation = Input.IndexOf("...");
|
|
int StarLocation = Input.IndexOf("*");
|
|
// hjandle various combos of -1 and non -1
|
|
int WildcardLocation = (DotsLocation >= 0 && StarLocation >= 0) ? Math.Min(DotsLocation, StarLocation) : Math.Max(DotsLocation, StarLocation);
|
|
if (WildcardLocation != -1)
|
|
{
|
|
// chop down to the last / before the wildcard
|
|
int LastSlashLocation = Input.Substring(0, WildcardLocation).Replace("\\", "/").LastIndexOf("/");
|
|
Input = Input.Substring(0, LastSlashLocation);
|
|
}
|
|
|
|
return Input;
|
|
}
|
|
|
|
|
|
static private P4Connection PerforceConnection = null;
|
|
static private P4ClientInfo PerforceClient = null;
|
|
// only non-null if we are connected
|
|
static private string ConnectedRoot = null;
|
|
|
|
private P4ClientInfo DetectClientForStream(string Stream, string Username, string Hostname)
|
|
{
|
|
|
|
// find clients for this user in the given stream
|
|
P4ClientInfo[] StreamClients = PerforceConnection.GetClientsForUser(Username, null, Stream);
|
|
|
|
// find the first one usable on this host
|
|
foreach (P4ClientInfo Client in StreamClients)
|
|
{
|
|
if (string.IsNullOrEmpty(Client.Host) || string.Compare(Client.Host, Hostname) == 0)
|
|
{
|
|
return Client;
|
|
}
|
|
}
|
|
|
|
// if no clients at all, return null
|
|
return null;
|
|
}
|
|
|
|
private P4ClientInfo CanClientHandleOperation(string ClientName, string Operation, string Hostname)
|
|
{
|
|
// make sure the Operation can be supported by the client (Tokens[1] is the clientspec name)
|
|
IProcessResult P4Result = PerforceConnection.P4(string.Format("-c {0}", ClientName), string.Format("where {0}", Operation), Input: null, AllowSpew: false, WithClient: false);
|
|
|
|
if (P4Result.ExitCode == 0)
|
|
{
|
|
// sadly, this doesn't set an ExitCode, so we have to look at output
|
|
if (!P4Result.Output.Trim().EndsWith("not in client view."))
|
|
{
|
|
P4ClientInfo ClientInfo = PerforceConnection.GetClientInfo(ClientName, true);
|
|
// make sure it's usable on this computer
|
|
if (string.IsNullOrEmpty(ClientInfo.Host) || string.Compare(ClientInfo.Host, Hostname) == 0)
|
|
{
|
|
return ClientInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private P4ClientInfo DetectClientForDepot(string Operation, string Username, string Hostname)
|
|
{
|
|
// use the bits up to a wildcard
|
|
Operation = GetDirectoryBeforeWildcard(Operation);
|
|
|
|
P4ClientInfo Client;
|
|
if (TurnkeySettings.HasSetUserSetting("User_LastPerforceClient"))
|
|
{
|
|
Client = CanClientHandleOperation(TurnkeySettings.GetUserSetting("User_LastPerforceClient"), Operation, Hostname);
|
|
if (Client != null)
|
|
{
|
|
return Client;
|
|
}
|
|
}
|
|
|
|
// Get all clients for this user
|
|
string P4Command = String.Format("clients -u {0}", Username);
|
|
|
|
// @todo turnkey: sort the results on third token (date)
|
|
var P4Result = PerforceConnection.P4(string.Format("clients -u {0}", Username), AllowSpew: false, WithClient: false);
|
|
if (P4Result.ExitCode != 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Parse output.
|
|
var Lines = P4Result.Output.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
|
foreach (string Line in Lines)
|
|
{
|
|
var Tokens = Line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
if (Tokens[0] == "Client")
|
|
{
|
|
Client = CanClientHandleOperation(Tokens[1], Operation, Hostname);
|
|
if (Client != null)
|
|
{
|
|
// remember this for next time
|
|
TurnkeySettings.SetUserSetting("User_LastPerforceClient", Tokens[1]);
|
|
|
|
return Client;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if no clients at all, return null
|
|
return null;
|
|
}
|
|
|
|
private bool PrepareForOperation(string Operation)
|
|
{
|
|
Match StreamMatch = new Regex(@"(\/\/\w*\/\w*)\/.*").Match(Operation);
|
|
if (!StreamMatch.Success)
|
|
{
|
|
throw new AutomationException("Unable to find stream spec in perforce operation {0}", Operation);
|
|
}
|
|
|
|
string SpecRoot = StreamMatch.Groups[1].ToString();
|
|
string DepotName = SpecRoot.Substring(0, SpecRoot.LastIndexOf('/'));
|
|
string Hostname = System.Net.Dns.GetHostName();
|
|
|
|
// @todo turnkey - for depot types, we can't totally safely check DepotName, we need to make sure the recent Client can handle the Operation - which we could with tricky View parsing
|
|
if (ConnectedRoot != SpecRoot || PerforceConnection == null)
|
|
{
|
|
TurnkeyUtils.Log("Finding clientspec usable with {0}...", Operation);
|
|
|
|
PerforceConnection = new P4Connection(null, null);
|
|
string Username = P4Environment.DetectUserName(PerforceConnection);
|
|
|
|
// make sure it's a stream
|
|
var P4Result = PerforceConnection.P4("stream -o " + SpecRoot, AllowSpew: false);
|
|
bool bIsStream = P4Result.ExitCode == 0;
|
|
|
|
// hunt down a client that can be used
|
|
PerforceClient = bIsStream ? DetectClientForStream(SpecRoot, Username, Hostname) : DetectClientForDepot(Operation, Username, Hostname);
|
|
|
|
if (PerforceClient != null)
|
|
{
|
|
TurnkeyUtils.Log("Using client {0}", PerforceClient.Name);
|
|
}
|
|
else
|
|
{
|
|
TurnkeyUtils.Log("Unable to find a clientspec for the perforce operation {0}, looking for a depot client", Operation);
|
|
|
|
bool bResponse = TurnkeyUtils.GetUserConfirmation("Would you like to create one?", false);
|
|
if (bResponse == false)
|
|
{
|
|
// make sure to try again next time
|
|
PerforceConnection = null;
|
|
TurnkeyUtils.Log("Skipping operation");
|
|
return false;
|
|
}
|
|
|
|
// get clientspec name
|
|
string ClientName = TurnkeyUtils.ReadInput("Enter clientspec name:", string.Format("{0}_sdks", Username));
|
|
|
|
// get local pathname
|
|
// if we had UE_SDKS_ROOT already set, we use it as a default
|
|
string AutoSdksDir = Environment.GetEnvironmentVariable("UE_SDKS_ROOT");
|
|
string DefaultDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "AutoSdks");
|
|
if (!string.IsNullOrEmpty(AutoSdksDir))
|
|
{
|
|
// @todo turnkey - for a deeper directory structure, we should match directory names to the perforce Operation, to figure it out properly
|
|
DefaultDir = Path.GetDirectoryName(AutoSdksDir);
|
|
}
|
|
string LocalPath = TurnkeyUtils.ReadInput(string.Format("Enter where to map {0} on your computer:", bIsStream ? SpecRoot : DepotName), DefaultDir);
|
|
|
|
// create a client from the input settings
|
|
P4ClientInfo NewClient = new P4ClientInfo();
|
|
NewClient.Name = ClientName;
|
|
NewClient.Owner = Username;
|
|
NewClient.Host = Hostname;
|
|
NewClient.RootPath = LocalPath;
|
|
NewClient.Options |= P4ClientOption.RmDir;
|
|
if (bIsStream)
|
|
{
|
|
NewClient.Stream = SpecRoot;
|
|
}
|
|
else
|
|
{
|
|
// set up mapping (CreateClient prepends the Key with //<clientname>)
|
|
NewClient.View.Add(new KeyValuePair<string, string>(DepotName + "/...", "/..."));
|
|
}
|
|
|
|
PerforceClient = PerforceConnection.CreateClient(NewClient);
|
|
}
|
|
|
|
if (PerforceClient == null)
|
|
{
|
|
TurnkeyUtils.Log("Unable to find or create a client, will not perform perforce copy operation {0}", Operation);
|
|
return false;
|
|
}
|
|
|
|
// now connect to that
|
|
PerforceConnection = new P4Connection(Username, PerforceClient.Name);
|
|
|
|
// @todo turnkey: how to check that for errors?
|
|
|
|
ConnectedRoot = bIsStream ? SpecRoot : DepotName;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
}
|
|
}
|