diff --git a/dotnet/Microsoft.iOS.Windows.Sdk/targets/Microsoft.iOS.Windows.Sdk.props b/dotnet/Microsoft.iOS.Windows.Sdk/targets/Microsoft.iOS.Windows.Sdk.props
index f834eba64dbd..57f86c3eed67 100644
--- a/dotnet/Microsoft.iOS.Windows.Sdk/targets/Microsoft.iOS.Windows.Sdk.props
+++ b/dotnet/Microsoft.iOS.Windows.Sdk/targets/Microsoft.iOS.Windows.Sdk.props
@@ -8,6 +8,7 @@
<_DotNetRootRemoteDirectory Condition="$(_DotNetRootRemoteDirectory) == ''">/usr/local/share/dotnet/
<_XamarinSdkRootDirectoryOnMac Condition="'$(_XamarinSdkRootDirectory)' != ''">$(_XamarinSdkRootDirectory.Replace('$(NetCoreRoot)', '$(_DotNetRootRemoteDirectory)'))
<_MlaunchPath Condition="'$(_MlaunchPath)' == ''">$(_XamarinSdkRootDirectoryOnMac)tools/bin/mlaunch
+ $(_XamarinSdkRootDirectoryOnMac)tools/bin/mlaunch
$(AfterMicrosoftNETSdkTargets);$(MSBuildThisFileDirectory)..\targets\Microsoft.iOS.Windows.Sdk.targets
<_XamarinSdkRootOnMac Condition="'$(_XamarinSdkRoot)' != ''">$(_XamarinSdkRoot.Replace('$(NetCoreRoot)', '$(_DotNetRootRemoteDirectory)'))
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets
index dc0b2f67ca28..728d6c4237d0 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.targets
+++ b/dotnet/targets/Xamarin.Shared.Sdk.targets
@@ -1817,34 +1817,51 @@
- <_MlaunchPath Condition="'$(_MlaunchPath)' == ''">$(_XamarinSdkRootDirectory)tools\bin\mlaunch
+
+ $(_MlaunchPath)
+ $(_XamarinSdkRootDirectory)tools\bin\mlaunch
+ <_MlaunchPath Condition="'$(_MlaunchPath)' == ''">$(MlaunchPath)
-
+
+
+
+
-
+
-
+
+
+
+
+
-
-
+
+
+
<_MlaunchCaptureOutput Condition="'$(_MlaunchCaptureOutput)' == ''">true
@@ -1869,7 +1886,7 @@
DeviceName="$(_DeviceName)"
EnvironmentVariables="@(MlaunchEnvironmentVariables)"
LaunchApp="$(_AppBundlePath)"
- MlaunchPath="$(_MlaunchPath)"
+ MlaunchPath="$(MlaunchPath)"
SdkIsSimulator="$(_SdkIsSimulator)"
SdkDevPath="$(_SdkDevPath)"
SdkVersion="$(_SdkVersion)"
@@ -1878,12 +1895,23 @@
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
WaitForExit="$(_MlaunchWaitForExit)"
>
-
+
+
+
+
+
+
- $(_MlaunchPath)
- $(_MlaunchRunArguments)
+ $(MlaunchPath)
+ $(MlaunchRunArguments)
diff --git a/tests/common/BinLog.cs b/tests/common/BinLog.cs
index 43a5d42e0ba7..36fdc413452d 100644
--- a/tests/common/BinLog.cs
+++ b/tests/common/BinLog.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
@@ -239,6 +240,46 @@ public static IEnumerable GetBuildMessages (string path)
}
}
+#if NET
+ public static bool TryFindPropertyValue (string binlog, string property, [NotNullWhen (true)] out string? value)
+#else
+ public static bool TryFindPropertyValue (string binlog, string property, out string? value)
+#endif
+ {
+ value = null;
+
+ var reader = new BinLogReader ();
+ foreach (var record in reader.ReadRecords (binlog)) {
+ var args = record?.Args;
+ if (args is null)
+ continue;
+ if (args is PropertyInitialValueSetEventArgs pivsea) {
+ if (string.Equals (property, pivsea.PropertyName, StringComparison.OrdinalIgnoreCase))
+ value = pivsea.PropertyValue;
+ } else if (args is PropertyReassignmentEventArgs prea) {
+ if (string.Equals (property, prea.PropertyName, StringComparison.OrdinalIgnoreCase))
+ value = prea.NewValue;
+ } else if (args is ProjectEvaluationFinishedEventArgs pefea) {
+ var dict = pefea.Properties as IDictionary;
+ if (dict is not null && dict.TryGetValue (property, out var pvalue))
+ value = pvalue;
+ } else if (args is BuildMessageEventArgs bmea) {
+ if (bmea.Message.StartsWith ("Output Property: ", StringComparison.Ordinal)) {
+ var kvp = bmea.Message.Substring ("Output Property: ".Length);
+ var eq = kvp.IndexOf ('=');
+ if (eq > 0) {
+ var propname = kvp.Substring (0, eq);
+ var propvalue = kvp.Substring (eq + 1);
+ if (propname == property)
+ value = propvalue;
+ }
+ }
+ }
+ }
+
+ return value is not null;
+ }
+
// Returns a diagnostic build log as a string
public static string PrintToString (string path)
{
diff --git a/tests/dotnet/UnitTests/MlaunchTest.cs b/tests/dotnet/UnitTests/MlaunchTest.cs
new file mode 100644
index 000000000000..d04a78d52ec8
--- /dev/null
+++ b/tests/dotnet/UnitTests/MlaunchTest.cs
@@ -0,0 +1,97 @@
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Text;
+
+using Mono.Cecil;
+
+using Xamarin.Tests;
+
+#nullable enable
+
+namespace Xamarin.Tests {
+ [TestFixture]
+ public class MlaunchTest : TestBaseClass {
+ [Test]
+ [TestCase (ApplePlatform.iOS, "ios-arm64")]
+ [TestCase (ApplePlatform.TVOS, "tvos-arm64")]
+ public void GetMlaunchInstallArguments (ApplePlatform platform, string runtimeIdentifiers)
+ {
+ var project = "MySimpleApp";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+
+ var outputPath = Path.Combine (Cache.CreateTemporaryDirectory (), "install.sh");
+ var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties ["EnableCodeSigning"] = "false"; // Skip code signing, since that would require making sure we have code signing configured on bots.
+
+ // Create the app manifest first, since it's required to compute the mlaunch install arguments
+ DotNet.Execute ("build", project_path, properties, target: "_DetectSdkLocations;_DetectAppManifest;_CompileAppManifest;_WriteAppManifest");
+
+ properties ["MlaunchInstallScript"] = outputPath;
+ var rv = DotNet.Execute ("build", project_path, properties, target: "ComputeMlaunchInstallArguments");
+
+ if (!BinLog.TryFindPropertyValue (rv.BinLogPath, "MlaunchInstallArguments", out var mlaunchInstallArguments))
+ Assert.Fail ("Could not find the property 'MlaunchInstallArguments' in the binlog.");
+
+ if (!BinLog.TryFindPropertyValue (rv.BinLogPath, "MlaunchPath", out var mlaunchPath))
+ Assert.Fail ("Could not find the property 'MlaunchPath' in the binlog.");
+ Assert.That (mlaunchPath, Does.Exist, "mlaunch existence");
+
+ var expectedArguments = new StringBuilder ();
+ expectedArguments.Append ("--installdev ");
+ expectedArguments.Append (appPath.Substring (Path.GetDirectoryName (project_path)!.Length + 1)).Append ('/');
+ expectedArguments.Append ($" --wait-for-exit:false");
+ Assert.AreEqual (expectedArguments.ToString (), mlaunchInstallArguments);
+
+ var scriptContents = File.ReadAllText (outputPath).Trim ('\n'); ;
+ var expectedScriptContents = mlaunchPath + " " + expectedArguments.ToString ();
+ Assert.AreEqual (expectedScriptContents, scriptContents, "Script contents");
+ }
+
+ [Test]
+ [TestCase (ApplePlatform.iOS, "iossimulator-x64;iossimulator-arm64", ":v2:runtime=com.apple.CoreSimulator.SimRuntime.iOS-16-4,devicetype=com.apple.CoreSimulator.SimDeviceType.iPhone-14-Pro")]
+ [TestCase (ApplePlatform.iOS, "ios-arm64", "")]
+ [TestCase (ApplePlatform.TVOS, "tvossimulator-arm64", ":v2:runtime=com.apple.CoreSimulator.SimRuntime.tvOS-16-4,devicetype=com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-1080p")]
+ public void GetMlaunchRunArguments (ApplePlatform platform, string runtimeIdentifiers, string device)
+ {
+ var project = "MySimpleApp";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+
+ var outputPath = Path.Combine (Cache.CreateTemporaryDirectory (), "launch.sh");
+ var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties ["EnableCodeSigning"] = "false"; // Skip code signing, since that would require making sure we have code signing configured on bots.
+
+ // Create the app manifest first, since it's required to compute the mlaunch run arguments
+ DotNet.Execute ("build", project_path, properties, target: "_DetectSdkLocations;_DetectAppManifest;_CompileAppManifest;_WriteAppManifest");
+
+ properties ["MlaunchRunScript"] = outputPath;
+ var rv = DotNet.Execute ("build", project_path, properties, target: "ComputeMlaunchRunArguments");
+
+ if (!BinLog.TryFindPropertyValue (rv.BinLogPath, "MlaunchRunArguments", out var mlaunchRunArguments))
+ Assert.Fail ("Could not find the property 'MlaunchRunArguments' in the binlog.");
+
+ if (!BinLog.TryFindPropertyValue (rv.BinLogPath, "MlaunchPath", out var mlaunchPath))
+ Assert.Fail ("Could not find the property 'MlaunchPath' in the binlog.");
+ Assert.That (mlaunchPath, Does.Exist, "mlaunch existence");
+
+ var expectedArguments = new StringBuilder ();
+ var isSim = runtimeIdentifiers.Contains ("simulator");
+ expectedArguments.Append (isSim ? "--launchsim " : "--launchdev ");
+ expectedArguments.Append (appPath.Substring (Path.GetDirectoryName (project_path)!.Length + 1)).Append ('/');
+ if (isSim) {
+ expectedArguments.Append (" --device \"");
+ expectedArguments.Append (device);
+ expectedArguments.Append ('"');
+ }
+ expectedArguments.Append ($" --wait-for-exit:true");
+ Assert.AreEqual (expectedArguments.ToString (), mlaunchRunArguments);
+
+ var scriptContents = File.ReadAllText (outputPath).Trim ('\n'); ;
+ var expectedScriptContents = mlaunchPath + " " + expectedArguments.ToString ();
+ Assert.AreEqual (expectedScriptContents, scriptContents, "Script contents");
+ }
+ }
+}