// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
namespace EpicGames.Perforce
{
///
/// Wraps a call to a p4.exe child process, and allows reading data from it
///
class PerforceChildProcess : IPerforceOutput, IDisposable
{
///
/// The process group
///
ManagedProcessGroup _childProcessGroup;
///
/// The child process instance
///
ManagedProcess _childProcess;
///
/// Scope object for tracing
///
ITraceSpan _scope;
///
/// The buffer data
///
byte[] _buffer;
///
/// End of the valid portion of the buffer (exclusive)
///
int _bufferEnd;
///
public ReadOnlyMemory Data => _buffer.AsMemory(0, _bufferEnd);
///
/// Temp file containing file arguments
///
string? _tempFileName;
///
/// Constructor
///
///
/// Command line arguments
/// File arguments, which may be placed in a response file
/// Input data to pass to the child process
///
/// Logging device
public PerforceChildProcess(string command, IReadOnlyList arguments, IReadOnlyList? fileArguments, byte[]? inputData, IReadOnlyList globalOptions, ILogger logger)
{
string perforceFileName = GetExecutable();
List fullArguments = new List();
fullArguments.Add("-G");
fullArguments.AddRange(globalOptions);
if (fileArguments != null)
{
_tempFileName = Path.GetTempFileName();
File.WriteAllLines(_tempFileName, fileArguments);
fullArguments.Add($"-x{_tempFileName}");
}
fullArguments.Add(command);
fullArguments.AddRange(arguments);
string fullArgumentList = CommandLineArguments.Join(fullArguments);
logger.LogDebug("Running {Executable} {Arguments}", perforceFileName, fullArgumentList);
_scope = TraceSpan.Create(command, service: "perforce");
_scope.AddMetadata("arguments", fullArgumentList);
_childProcessGroup = new ManagedProcessGroup();
_childProcess = new ManagedProcess(_childProcessGroup, perforceFileName, fullArgumentList, null, null, inputData, ProcessPriorityClass.Normal);
_buffer = new byte[64 * 1024];
}
///
/// Gets the path to the P4.EXE executable
///
/// Path to the executable
public static string GetExecutable()
{
string perforceFileName = "p4.exe";
if (!OperatingSystem.IsWindows())
{
string[] p4Paths = {
"/usr/bin/p4", // Default path
"/opt/homebrew/bin/p4", // Apple Silicon Homebrew Path
"/usr/local/bin/p4", // Apple Intel Homebrew Path
"/home/linuxbrew/.linuxbrew/bin/p4" }; // Linux Homebrew path
foreach (string path in p4Paths)
{
if (File.Exists(path))
{
perforceFileName = path;
break;
}
}
}
return perforceFileName;
}
///
public ValueTask DisposeAsync()
{
Dispose();
return new ValueTask(Task.CompletedTask);
}
///
public void Dispose()
{
if (_childProcess != null)
{
_childProcess.Dispose();
_childProcess = null!;
}
if (_childProcessGroup != null)
{
_childProcessGroup.Dispose();
_childProcessGroup = null!;
}
if (_scope != null)
{
_scope.Dispose();
_scope = null!;
}
if (_tempFileName != null)
{
try
{
File.Delete(_tempFileName);
}
catch { }
_tempFileName = null;
}
}
///
public async Task ReadAsync(CancellationToken cancellationToken)
{
// Update the buffer contents
if (_bufferEnd == _buffer.Length)
{
Array.Resize(ref _buffer, Math.Min(_buffer.Length + (32 * 1024 * 1024), _buffer.Length * 2));
}
// Try to read more data
int prevBufferEnd = _bufferEnd;
while (_bufferEnd < _buffer.Length)
{
int count = await _childProcess!.ReadAsync(_buffer, _bufferEnd, _buffer.Length - _bufferEnd, cancellationToken);
if (count == 0)
{
break;
}
_bufferEnd += count;
}
return _bufferEnd > prevBufferEnd;
}
///
public void Discard(int numBytes)
{
if (numBytes > 0)
{
Array.Copy(_buffer, numBytes, _buffer, 0, _bufferEnd - numBytes);
_bufferEnd -= numBytes;
}
}
///
/// Reads all output from the child process as a string
///
/// Cancellation token to abort the read
/// Exit code and output from the process
public async Task> TryReadToEndAsync(CancellationToken cancellationToken)
{
using MemoryStream stream = new MemoryStream();
while (await ReadAsync(cancellationToken))
{
ReadOnlyMemory dataCopy = Data;
stream.Write(dataCopy.Span);
Discard(dataCopy.Length);
}
string @string = Encoding.Default.GetString(stream.ToArray());
return Tuple.Create(_childProcess.ExitCode == 0, @string);
}
}
}