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"); + } + } +}