// 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 { /// /// Base class for Apple-based project generators /// abstract class AppleProjectGenerator : PlatformProjectGenerator { private static readonly XcrunRunner AppleHelper = new XcrunRunner(); /// protected AppleProjectGenerator(CommandLineArguments Arguments, ILogger Logger) : base(Arguments, Logger) { } /// public override bool HasVisualStudioSupport(VSSettings InVSSettings) { return false; } /// public override IList 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(0); } private sealed class XcrunRunner : IDisposable { private readonly Dictionary> CachedIncludePaths = new Dictionary>(); private string CurrentlyProcessedSDK = String.Empty; private Process? XcrunProcess; private bool IsReadingIncludesSection; /// public void Dispose() { XcrunProcess?.Kill(true); XcrunProcess?.Dispose(); XcrunProcess = null; } public IList 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(); 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; } } } }