Files
UnrealEngine/Engine/Source/Programs/UnrealBuildTool/Platform/Apple/AppleProjectGenerator.cs
2025-05-18 13:04:45 +08:00

157 lines
5.0 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
namespace UnrealBuildTool
{
/// <summary>
/// Base class for Apple-based project generators
/// </summary>
abstract class AppleProjectGenerator : PlatformProjectGenerator
{
private static readonly XcrunRunner AppleHelper = new XcrunRunner();
/// <inheritdoc/>
protected AppleProjectGenerator(CommandLineArguments Arguments, ILogger Logger)
: base(Arguments, Logger)
{
}
/// <inheritdoc/>
public override bool HasVisualStudioSupport(VSSettings InVSSettings)
{
return false;
}
/// <inheritdoc/>
public override IList<string> GetSystemIncludePaths(UEBuildTarget InTarget)
{
if (UEBuildPlatform.IsPlatformInGroup(BuildHostPlatform.Current.Platform, UnrealPlatformGroup.Apple))
{
// Only generate Apple system include paths when host platform is Apple OS
return AppleHelper.GetAppleSystemIncludePaths(InTarget.Architectures.SingleArchitecture, InTarget.Platform, Logger);
}
// TODO: Fix case when working with MacOS on Windows host platform
return new List<string>(0);
}
private sealed class XcrunRunner : IDisposable
{
private readonly Dictionary<string, IList<string>> CachedIncludePaths = new Dictionary<string, IList<string>>();
private string CurrentlyProcessedSDK = String.Empty;
private Process? XcrunProcess;
private bool IsReadingIncludesSection;
/// <inheritdoc/>
public void Dispose()
{
XcrunProcess?.Kill(true);
XcrunProcess?.Dispose();
XcrunProcess = null;
}
public IList<string> GetAppleSystemIncludePaths(UnrealArch Architecture, UnrealTargetPlatform Platform, ILogger Logger)
{
if (!UEBuildPlatform.IsPlatformInGroup(Platform, UnrealPlatformGroup.Apple))
{
throw new InvalidOperationException("xcrun can be run only for Apple's platforms");
}
string SDKPath = GetSDKPath(Architecture, Platform, Logger);
if (!CachedIncludePaths.ContainsKey(SDKPath))
{
CalculateSystemIncludePaths(SDKPath, Logger);
}
return CachedIncludePaths[SDKPath];
}
private void CalculateSystemIncludePaths(string SDKPath, ILogger Logger)
{
if (!String.IsNullOrEmpty(CurrentlyProcessedSDK))
{
throw new InvalidOperationException("Cannot calculate include paths for several platforms at once");
}
CurrentlyProcessedSDK = SDKPath;
CachedIncludePaths[SDKPath] = new List<string>();
using (XcrunProcess = new Process())
{
string AppName = "xcrun";
string SystemRootArgument = String.IsNullOrEmpty(SDKPath) ? String.Empty : (" -isysroot " + SDKPath);
string Arguments = "clang++ -Wp,-v -x c++ - -fsyntax-only" + SystemRootArgument;
XcrunProcess.StartInfo.FileName = AppName;
XcrunProcess.StartInfo.Arguments = Arguments;
XcrunProcess.StartInfo.UseShellExecute = false;
XcrunProcess.StartInfo.CreateNoWindow = true;
// For some weird reason output of this command is written to error channel so we're redirecting both channels
XcrunProcess.StartInfo.RedirectStandardOutput = true;
XcrunProcess.StartInfo.RedirectStandardError = true;
XcrunProcess.OutputDataReceived += OnOutputDataReceived;
XcrunProcess.ErrorDataReceived += OnOutputDataReceived;
XcrunProcess.Start();
XcrunProcess.BeginOutputReadLine();
XcrunProcess.BeginErrorReadLine();
// xcrun is not finished on it's own. It should be killed by OnOutputDataReceived when reading is finished. But we'll add timeout as a safeguard.
// While usually it is fast, first launch on macOS might take ~10 seconds so timeout is quite big: https://github.com/llvm/llvm-project/issues/75179
bool HasExited = XcrunProcess.WaitForExit(30_000);
if (!HasExited)
{
Logger.LogWarning("xcrun didn't finish in 30 second. List of system include paths will not be complete");
XcrunProcess.Kill();
}
}
XcrunProcess = null;
IsReadingIncludesSection = false;
CurrentlyProcessedSDK = String.Empty;
}
private void OnOutputDataReceived(object Sender, DataReceivedEventArgs Args)
{
if (Args.Data != null)
{
if (IsReadingIncludesSection)
{
if (Args.Data.StartsWith("End of search"))
{
IsReadingIncludesSection = false;
XcrunProcess!.Kill();
}
else
{
if (!Args.Data.EndsWith("(framework directory)"))
{
CachedIncludePaths[CurrentlyProcessedSDK].Add(Args.Data.Trim(' ', '"'));
}
}
}
if (Args.Data.StartsWith("#include <...>"))
{
IsReadingIncludesSection = true;
}
}
}
private string GetSDKPath(UnrealArch Architecture, UnrealTargetPlatform Platform, ILogger Logger)
{
if (Platform == UnrealTargetPlatform.Mac)
{
return ApplePlatformSDK.GetPlatformSDKDirectory("MacOSX").FullName;
}
return ApplePlatformSDK.GetPlatformSDKDirectory(Architecture == UnrealArch.IOSSimulator ? "iPhoneSimulator" : "iPhoneOS").FullName;
}
}
}
}