Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1627,4 +1627,22 @@
<data name="E7169" xml:space="preserve">
<value>The task '{0}' requires the property '{1}' to be set. Please file an issue at https://github.com/dotnet/macios/issues/new/choose.</value>
</data>

<data name="E7170" xml:space="preserve">
<value>The {0} simulator runtime is not installed. This is required by Apple's development tools (even when building for physical devices). Install it by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings &gt; Components).</value>
<comment>Shown when the required simulator runtime is not installed.
{0} - The platform name (e.g. "iOS" or "tvOS").</comment>
</data>

<data name="W7171" xml:space="preserve">
<value>Unable to determine if the {0} simulator runtime is installed. If the build fails or hangs, install the {0} simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line.</value>
<comment>Shown when we're unable to check for simulator runtime availability.
{0} - The platform name (e.g. "iOS" or "tvOS").</comment>
</data>

<data name="E7172" xml:space="preserve">
<value>The installed {0} simulator runtime is not compatible with the current Xcode version. Update the simulator runtime by running 'xcodebuild -downloadPlatform {0}' from the command line, or from Xcode (Settings &gt; Components).</value>
<comment>Shown when the tool reports a simulator runtime version mismatch.
{0} - The platform name (e.g. "iOS" or "tvOS").</comment>
</data>
</root>
112 changes: 108 additions & 4 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/XcodeCompilerToolTask.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text;
Expand All @@ -13,6 +14,7 @@
using Xamarin.Localization.MSBuild;

using Xamarin.MacDev;
using Xamarin.MacDev.Models;
Comment thread
rolfbjarne marked this conversation as resolved.
using Xamarin.Messaging.Build.Client;
using Xamarin.Utils;

Expand Down Expand Up @@ -154,6 +156,92 @@ static bool IsTranslated ()
return translated.Value;
}

// Cache simulator runtime check results to avoid running simctl multiple times.
// Key includes SdkDevPath because different Xcode installations may have different runtimes.
static ConcurrentDictionary<string, bool> simulatorRuntimeCache = new ();

/// <summary>
/// Returns the platform name used by simctl for the current build platform, or null if no simulator is needed.
/// </summary>
string? GetSimulatorPlatformName ()
{
switch (Platform) {
case ApplePlatform.iOS:
case ApplePlatform.MacCatalyst:
// Mac Catalyst uses the iOS-based toolchain, so it also needs the iOS simulator runtime.
return "iOS";
case ApplePlatform.TVOS:
return "tvOS";
case ApplePlatform.MacOSX:
default:
return null;
}
}

/// <summary>
/// Checks if the required simulator runtime is installed and emits a diagnostic if not.
/// Apple's Xcode tools (actool, ibtool, etc.) require the simulator runtime to function,
/// even when building for physical devices. Call this after a tool failure to provide
/// actionable guidance to the user.
/// </summary>
void CheckSimulatorRuntimeAvailable ()
{
var simPlatform = GetSimulatorPlatformName ();
if (simPlatform is null)
return;

var cacheKey = $"{simPlatform}:{SdkDevPath}";
if (simulatorRuntimeCache.TryGetValue (cacheKey, out var cachedResult)) {
if (!cachedResult)
Log.LogError (MSBStrings.E7170, simPlatform);
return;
}

var jsonOutputFile = Path.GetTempFileName ();
try {
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource (cancellationTokenSource.Token);
timeoutCts.CancelAfter (TimeSpan.FromMinutes (1));
var args = new List<string> {
"simctl",
"list",
"runtimes",
"-j",
"--json-output=" + jsonOutputFile
Comment thread
rolfbjarne marked this conversation as resolved.
};
var rv = ExecuteAsync ("xcrun", args, showErrorIfFailure: false, cancellationToken: timeoutCts.Token).Result;

if (rv.ExitCode != 0) {
Log.LogWarning (MSBStrings.W7171, simPlatform);
return;
}

var json = File.ReadAllText (jsonOutputFile);
var runtimes = SimctlOutputParser.ParseRuntimes (json);

var hasRuntime = runtimes.Any (r =>
string.Equals (r.Platform, simPlatform, StringComparison.OrdinalIgnoreCase) && r.IsAvailable);

simulatorRuntimeCache [cacheKey] = hasRuntime;
if (!hasRuntime)
Log.LogError (MSBStrings.E7170, simPlatform);
} catch (OperationCanceledException) when (cancellationTokenSource.IsCancellationRequested) {
// User cancelled - don't emit diagnostics
} catch (AggregateException ae) when (ae.InnerException is OperationCanceledException && cancellationTokenSource.IsCancellationRequested) {
// User cancelled - don't emit diagnostics
} catch (OperationCanceledException) {
// Timeout
Log.LogWarning (MSBStrings.W7171, simPlatform);
} catch (AggregateException ae) when (ae.InnerException is OperationCanceledException) {
// Timeout
Log.LogWarning (MSBStrings.W7171, simPlatform);
} catch (Exception ex) {
Log.LogWarning (MSBStrings.W7171, simPlatform);
Log.LogMessage (MessageImportance.Low, "Exception while checking simulator runtime: {0}", ex.Message);
} finally {
File.Delete (jsonOutputFile);
}
}

protected int Compile (ITaskItem [] items, string output, ITaskItem manifest)
{
var environment = new Dictionary<string, string?> ();
Expand Down Expand Up @@ -193,7 +281,7 @@ protected int Compile (ITaskItem [] items, string output, ITaskItem manifest)
if (Log.HasLoggedErrors)
return 1;

var rv = ExecuteAsync (executable, args, environment: environment, cancellationToken: cancellationTokenSource.Token).Result;
var rv = ExecuteAsync (executable, args, showErrorIfFailure: true, environment: environment, cancellationToken: cancellationTokenSource.Token).Result;
var exitCode = rv.ExitCode;
var messages = rv.Output.StandardOutput;
File.WriteAllText (manifest.ItemSpec, messages);
Expand All @@ -206,8 +294,6 @@ protected int Compile (ITaskItem [] items, string output, ITaskItem manifest)
if (errors.Length > 0)
Log.LogError (null, null, null, items [0].ItemSpec, 0, 0, 0, 0, "{0}", errors);

Log.LogError (MSBStrings.E0117, ToolName, exitCode);

// Note: If the log file exists and is parseable, log those warnings/errors as well...
if (File.Exists (manifest.ItemSpec)) {
try {
Expand All @@ -220,6 +306,9 @@ protected int Compile (ITaskItem [] items, string output, ITaskItem manifest)

File.Delete (manifest.ItemSpec);
}

// Check if the failure might be caused by a missing simulator runtime.
CheckSimulatorRuntimeAvailable ();
}

return exitCode;
Expand Down Expand Up @@ -285,8 +374,14 @@ protected void LogWarningsAndErrors (PDictionary plist, ITaskItem file)

if (plist.TryGetValue (string.Format ("com.apple.{0}.errors", ToolName), out array)) {
foreach (var item in array.OfType<PDictionary> ()) {
if (item.TryGetValue ("description", out message))
if (item.TryGetValue ("description", out message)) {
Log.LogError (ToolName, null, null, file.ItemSpec, 0, 0, 0, 0, "{0}", message.Value);
if (IsSimulatorRuntimeVersionError (message.Value)) {
var simPlatform = GetSimulatorPlatformName ();
if (simPlatform is not null)
Log.LogError (MSBStrings.E7172, simPlatform);
}
}
}
}

Expand All @@ -298,6 +393,15 @@ protected void LogWarningsAndErrors (PDictionary plist, ITaskItem file)
}
}

/// <summary>
/// Detects error messages like "No simulator runtime version from [...] available to use with ... SDK version ..."
/// which indicate an incompatible or missing simulator runtime version.
/// </summary>
static bool IsSimulatorRuntimeVersionError (string message)
{
return message.IndexOf ("simulator runtime", StringComparison.OrdinalIgnoreCase) >= 0;
}
Comment thread
rolfbjarne marked this conversation as resolved.

public void Cancel ()
{
if (ShouldExecuteRemotely ()) {
Expand Down
Loading