214 lines
7.0 KiB
C#
214 lines
7.0 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
using EpicGames.Core;
|
|
using EpicGames.Perforce;
|
|
using Microsoft.Extensions.Logging;
|
|
using UnrealGameSync;
|
|
using UnrealGameSyncCmd.Utils;
|
|
|
|
using UserErrorException = UnrealGameSyncCmd.Exceptions.UserErrorException;
|
|
|
|
namespace UnrealGameSyncCmd.Commands
|
|
{
|
|
internal class SyncCommandOptions
|
|
{
|
|
[CommandLine("-Clean")]
|
|
public bool Clean { get; set; }
|
|
|
|
[CommandLine("-Build")]
|
|
public bool Build { get; set; }
|
|
|
|
[CommandLine("-Binaries")]
|
|
public bool Binaries { get; set; }
|
|
|
|
[CommandLine("-NoGPF", Value = "false")]
|
|
[CommandLine("-NoProjectFiles", Value = "false")]
|
|
public bool ProjectFiles { get; set; } = true;
|
|
|
|
[CommandLine("-Clobber")]
|
|
public bool Clobber { get; set; }
|
|
|
|
[CommandLine("-Refilter")]
|
|
public bool Refilter { get; set; }
|
|
|
|
[CommandLine("-Only")]
|
|
public bool SingleChange { get; set; }
|
|
}
|
|
|
|
internal class SyncCommand : Command
|
|
{
|
|
static async Task<bool> IsCodeChangeAsync(IPerforceConnection perforce, int change)
|
|
{
|
|
DescribeRecord describeRecord = await perforce.DescribeAsync(change);
|
|
return IsCodeChange(describeRecord);
|
|
}
|
|
|
|
static bool IsCodeChange(DescribeRecord describeRecord)
|
|
{
|
|
foreach (DescribeFileRecord file in describeRecord.Files)
|
|
{
|
|
if (PerforceUtils.CodeExtensions.Any(extension => file.DepotFile.EndsWith(extension, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override async Task ExecuteAsync(CommandContext context)
|
|
{
|
|
ILogger logger = context.Logger;
|
|
context.Arguments.TryGetPositionalArgument(out string? changeString);
|
|
|
|
SyncCommandOptions syncOptions = new SyncCommandOptions();
|
|
context.Arguments.ApplyTo(syncOptions);
|
|
|
|
context.Arguments.CheckAllArgumentsUsed();
|
|
|
|
UserWorkspaceSettings settings = UserSettingsUtils.ReadRequiredUserWorkspaceSettings();
|
|
using IPerforceConnection perforceClient = await PerforceConnectionUtils.ConnectAsync(settings, context.LoggerFactory);
|
|
WorkspaceStateWrapper state = await WorkspaceStateUtils.ReadWorkspaceState(perforceClient, settings, context.UserSettings, logger);
|
|
|
|
changeString ??= "latest";
|
|
|
|
ProjectInfo projectInfo = new ProjectInfo(settings.RootDir, state.Current);
|
|
UserProjectSettings projectSettings = context.UserSettings.FindOrAddProjectSettings(projectInfo, settings, logger);
|
|
|
|
ConfigFile projectConfig = await ConfigUtils.ReadProjectConfigFileAsync(perforceClient, projectInfo, logger, CancellationToken.None);
|
|
|
|
bool syncLatest = String.Equals(changeString, "latest", StringComparison.OrdinalIgnoreCase);
|
|
|
|
int change;
|
|
if (!Int32.TryParse(changeString, out change))
|
|
{
|
|
if (syncLatest)
|
|
{
|
|
List<ChangesRecord> changes = await perforceClient.GetChangesAsync(ChangesOptions.None, 1, ChangeStatus.Submitted, $"//{settings.ClientName}/...");
|
|
change = changes[0].Number;
|
|
}
|
|
else
|
|
{
|
|
throw new UserErrorException("Unknown change type for sync '{Change}'", changeString);
|
|
}
|
|
}
|
|
|
|
WorkspaceUpdateOptions options = syncOptions.SingleChange ? WorkspaceUpdateOptions.SyncSingleChange : WorkspaceUpdateOptions.Sync;
|
|
if (syncOptions.Clean)
|
|
{
|
|
options |= WorkspaceUpdateOptions.Clean;
|
|
}
|
|
if (syncOptions.Build)
|
|
{
|
|
options |= WorkspaceUpdateOptions.Build;
|
|
}
|
|
if (syncOptions.ProjectFiles)
|
|
{
|
|
options |= WorkspaceUpdateOptions.GenerateProjectFiles;
|
|
}
|
|
if (syncOptions.Clobber)
|
|
{
|
|
options |= WorkspaceUpdateOptions.Clobber;
|
|
}
|
|
if (syncOptions.Refilter)
|
|
{
|
|
options |= WorkspaceUpdateOptions.Refilter;
|
|
}
|
|
options |= WorkspaceUpdateContext.GetOptionsFromConfig(context.UserSettings.Global, settings);
|
|
options |= WorkspaceUpdateOptions.RemoveFilteredFiles;
|
|
|
|
string[] syncFilter = SyncFilterUtils.ReadSyncFilter(settings, context.UserSettings, projectConfig, state.Current.ProjectIdentifier);
|
|
|
|
using WorkspaceLock? workspaceLock = CreateWorkspaceLock(settings.RootDir);
|
|
if (workspaceLock != null && !await workspaceLock.TryAcquireAsync())
|
|
{
|
|
logger.LogError("Another process is already syncing this workspace.");
|
|
return;
|
|
}
|
|
|
|
WorkspaceUpdateContext updateContext = new WorkspaceUpdateContext(change, options, BuildConfig.Development, syncFilter, projectSettings.BuildSteps, null);
|
|
updateContext.PerforceSyncOptions = context.UserSettings.Global.Perforce;
|
|
|
|
if (syncOptions.Binaries)
|
|
{
|
|
List<BaseArchiveChannel> archives = await BaseArchive.EnumerateChannelsAsync(perforceClient, context.HordeClient, context.CloudStorage, projectConfig, state.Current.ProjectIdentifier, CancellationToken.None);
|
|
|
|
BaseArchiveChannel? editorArchiveInfo = archives.FirstOrDefault(x => x.Type == IArchiveChannel.EditorArchiveType);
|
|
if (editorArchiveInfo == null)
|
|
{
|
|
throw new UserErrorException("No editor archives found for project");
|
|
}
|
|
|
|
KeyValuePair<int, IArchive> revision = editorArchiveInfo.ChangeNumberToArchive.LastOrDefault(x => x.Key <= change);
|
|
if (revision.Key == 0)
|
|
{
|
|
throw new UserErrorException($"No editor archives found for CL {change}");
|
|
}
|
|
|
|
if (revision.Key < change)
|
|
{
|
|
int lastChange = revision.Key;
|
|
|
|
List<ChangesRecord> changeRecords = await perforceClient.GetChangesAsync(ChangesOptions.None, 1, ChangeStatus.Submitted, $"//{settings.ClientName}/...@{revision.Key + 1},{change}");
|
|
foreach (ChangesRecord changeRecord in changeRecords.OrderBy(x => x.Number))
|
|
{
|
|
if (await IsCodeChangeAsync(perforceClient, changeRecord.Number))
|
|
{
|
|
if (syncLatest)
|
|
{
|
|
updateContext.ChangeNumber = lastChange;
|
|
}
|
|
else
|
|
{
|
|
throw new UserErrorException($"No editor binaries found for CL {change} (last archive at CL {revision.Key}, but CL {changeRecord.Number} is a code change)");
|
|
}
|
|
break;
|
|
}
|
|
change = changeRecord.Number;
|
|
}
|
|
}
|
|
|
|
updateContext.Options |= WorkspaceUpdateOptions.SyncArchives;
|
|
updateContext.ArchiveTypeToArchive[IArchiveChannel.EditorArchiveType] = revision.Value;
|
|
}
|
|
|
|
WorkspaceUpdate update = new WorkspaceUpdate(updateContext);
|
|
(WorkspaceUpdateResult result, string message) = await update.ExecuteAsync(perforceClient.Settings, projectInfo, state, context.Logger, CancellationToken.None);
|
|
if (result == WorkspaceUpdateResult.FilesToClobber)
|
|
{
|
|
logger.LogWarning("The following files are modified in your workspace:");
|
|
foreach (string file in updateContext.ClobberFiles.Keys.OrderBy(x => x))
|
|
{
|
|
logger.LogWarning(" {File}", file);
|
|
}
|
|
logger.LogWarning("Use -Clobber to overwrite");
|
|
}
|
|
|
|
state.Modify(x => x.SetLastSyncState(result, updateContext, message));
|
|
|
|
if (result != WorkspaceUpdateResult.Success)
|
|
{
|
|
throw new UserErrorException("{Message} (Result: {Result})", message, result);
|
|
}
|
|
}
|
|
|
|
static WorkspaceLock? CreateWorkspaceLock(DirectoryReference rootDir)
|
|
{
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
return new WorkspaceLock(rootDir);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|