diff --git a/src/Common/Microsoft.Arcade.Common/Helpers.cs b/src/Common/Microsoft.Arcade.Common/Helpers.cs index 8ff68b4ffb1..9b038de3d45 100644 --- a/src/Common/Microsoft.Arcade.Common/Helpers.cs +++ b/src/Common/Microsoft.Arcade.Common/Helpers.cs @@ -53,22 +53,16 @@ public T MutexExec(Func function, string mutexName) } } + public T DirectoryMutexExec(Func function, string path) => + MutexExec( + function, + $"Global\\{ComputeSha256Hash(path)}"); + public T MutexExec(Func> function, string mutexName) => MutexExec(() => function().GetAwaiter().GetResult(), mutexName); // Can't await because of mutex - // This overload is here so that async Actions don't get routed to MutexExec(Func function, string path) - public void MutexExec(Func function, string mutexName) => - MutexExec(() => { function().GetAwaiter().GetResult(); return true; }, mutexName); - - public T DirectoryMutexExec(Func function, string path) => - MutexExec(function, $"Global\\{ComputeSha256Hash(path)}"); - public T DirectoryMutexExec(Func> function, string path) => - DirectoryMutexExec(() => function().GetAwaiter().GetResult(), path); // Can't await because of mutex - - // This overload is here so that async Actions don't get routed to DirectoryMutexExec(Func function, string path) - public void DirectoryMutexExec(Func function, string path) => - DirectoryMutexExec(() => { function().GetAwaiter().GetResult(); return true; }, path); + DirectoryMutexExec(() => function().GetAwaiter().GetResult(), path); // Can't await because of mutex } public static class KeyValuePairExtensions diff --git a/src/Common/Microsoft.Arcade.Common/IHelpers.cs b/src/Common/Microsoft.Arcade.Common/IHelpers.cs index e770c4fb2f6..366940ded91 100644 --- a/src/Common/Microsoft.Arcade.Common/IHelpers.cs +++ b/src/Common/Microsoft.Arcade.Common/IHelpers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Microsoft.Arcade.Common @@ -6,15 +6,10 @@ namespace Microsoft.Arcade.Common public interface IHelpers { string ComputeSha256Hash(string normalizedPath); - - T MutexExec(Func function, string mutexName); - T MutexExec(Func> function, string mutexName); - void MutexExec(Func function, string mutexName); - T DirectoryMutexExec(Func function, string path); T DirectoryMutexExec(Func> function, string path); - void DirectoryMutexExec(Func function, string path); - + T MutexExec(Func function, string mutexName); + T MutexExec(Func> function, string mutexName); string RemoveTrailingSlash(string directoryPath); } -} +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs b/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs index f1bc08541fb..ab050888458 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs +++ b/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs @@ -3,9 +3,7 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Net; using System.Threading.Tasks; -using Microsoft.Arcade.Common; using Microsoft.Build.Framework; namespace Microsoft.DotNet.Helix.Sdk @@ -17,12 +15,10 @@ public class CreateXHarnessAppleWorkItems : XHarnessTaskBase { private const string EntryPointScriptName = "xharness-helix-job.apple.sh"; private const string RunnerScriptName = "xharness-runner.apple.sh"; + private const int DefaultLaunchTimeoutInMinutes = 10; private const string LaunchTimeoutPropName = "LaunchTimeout"; - private const string TargetPropName = "Targets"; + private const string TargetsPropName = "Targets"; private const string IncludesTestRunnerPropName = "IncludesTestRunner"; - private const int DefaultLaunchTimeoutInMinutes = 10; - - private readonly IHelpers _helpers = new Arcade.Common.Helpers(); /// /// An array of one or more paths to iOS app bundles (folders ending with ".app" usually) @@ -36,26 +32,9 @@ public class CreateXHarnessAppleWorkItems : XHarnessTaskBase public string XcodeVersion { get; set; } /// - /// URL template to get the provisioning profile that will be used to sign the app from (in case of real device targets). - /// The URL is a template in the following format: - /// https://storage.azure.com/signing/NET_Apple_Development_{PLATFORM}.mobileprovision - /// - public string ProvisioningProfileUrl { get; set; } - - /// - /// Path where we can store intermediate files. + /// Path to the provisioning profile that will be used to sign the app (in case of real device targets). /// - public string TmpDir { get; set; } - - private enum TargetPlatform - { - iOS, - tvOS, - } - - private string GetProvisioningProfileFileName(TargetPlatform platform) => Path.GetFileName(GetProvisioningProfileUrl(platform)); - - private string GetProvisioningProfileUrl(TargetPlatform platform) => ProvisioningProfileUrl.Replace("{PLATFORM}", platform.ToString()); + public string ProvisioningProfilePath { get; set; } /// /// The main method of this MSBuild task which calls the asynchronous execution method and @@ -80,7 +59,6 @@ public override bool Execute() /// private async Task ExecuteAsync() { - DownloadProvisioningProfiles(); WorkItems = (await Task.WhenAll(AppBundles.Select(PrepareWorkItem))).Where(wi => wi != null).ToArray(); } @@ -105,14 +83,20 @@ private async Task PrepareWorkItem(ITaskItem appBundleItem) var (testTimeout, workItemTimeout, expectedExitCode) = ParseMetadata(appBundleItem); // Validation of any metadata specific to iOS stuff goes here - if (!appBundleItem.TryGetMetadata(TargetPropName, out string target)) + if (!appBundleItem.TryGetMetadata(TargetsPropName, out string targets)) { Log.LogError("'Targets' metadata must be specified - " + "expecting list of target device/simulator platforms to execute tests on (e.g. ios-simulator-64)"); return null; } - target = target.ToLowerInvariant(); + bool isDeviceTarget = targets.Contains("device"); + string provisioningProfileDest = Path.Combine(appFolderPath, "embedded.mobileprovision"); + if (isDeviceTarget && string.IsNullOrEmpty(ProvisioningProfilePath) && !File.Exists(provisioningProfileDest)) + { + Log.LogError("ProvisioningProfilePath parameter not set but required for real device targets!"); + return null; + } // Optional timeout for the how long it takes for the app to be installed, booted and tests start executing TimeSpan launchTimeout = TimeSpan.FromMinutes(DefaultLaunchTimeoutInMinutes); @@ -136,55 +120,24 @@ private async Task PrepareWorkItem(ITaskItem appBundleItem) if (includesTestRunner && expectedExitCode != 0) { - Log.LogWarning("The ExpectedExitCode property is ignored in the `apple test` scenario"); + Log.LogWarning("The ExpectedExitCode property is ignored in the `ios test` scenario"); } - bool isDeviceTarget = target.Contains("device"); - string provisioningProfileDest = Path.Combine(appFolderPath, "embedded.mobileprovision"); - - // Handle files needed for signing if (isDeviceTarget) { - if (string.IsNullOrEmpty(TmpDir)) - { - Log.LogError(nameof(TmpDir) + " parameter not set but required for real device targets!"); - return null; - } - - if (string.IsNullOrEmpty(ProvisioningProfileUrl) && !File.Exists(provisioningProfileDest)) - { - Log.LogError(nameof(ProvisioningProfileUrl) + " parameter not set but required for real device targets!"); - return null; - } - if (!File.Exists(provisioningProfileDest)) { - // StartsWith because suffix can be the target OS version - TargetPlatform? platform = null; - if (target.StartsWith("ios-device")) - { - platform = TargetPlatform.iOS; - } - else if (target.StartsWith("tvos-device")) - { - platform = TargetPlatform.tvOS; - } - - if (platform.HasValue) - { - string profilePath = Path.Combine(TmpDir, GetProvisioningProfileFileName(platform.Value)); - Log.LogMessage($"Adding provisioning profile `{profilePath}` into the app bundle at `{provisioningProfileDest}`"); - File.Copy(profilePath, provisioningProfileDest); - } + Log.LogMessage("Adding provisioning profile into the app bundle"); + File.Copy(ProvisioningProfilePath, provisioningProfileDest); } else { - Log.LogMessage($"Bundle already contains a provisioning profile at `{provisioningProfileDest}`"); + Log.LogMessage("Bundle already contains a provisioning profile"); } } string appName = Path.GetFileName(appBundleItem.ItemSpec); - string command = GetHelixCommand(appName, target, testTimeout, launchTimeout, includesTestRunner, expectedExitCode); + string command = GetHelixCommand(appName, targets, testTimeout, launchTimeout, includesTestRunner, expectedExitCode); string payloadArchivePath = await CreateZipArchiveOfFolder(appFolderPath); Log.LogMessage($"Creating work item with properties Identity: {workItemName}, Payload: {appFolderPath}, Command: {command}"); @@ -237,49 +190,5 @@ private async Task CreateZipArchiveOfFolder(string folderToZip) return outputZipPath; } - - private void DownloadProvisioningProfiles() - { - if (string.IsNullOrEmpty(ProvisioningProfileUrl)) - { - return; - } - - string[] targets = AppBundles - .Select(appBundle => appBundle.TryGetMetadata(TargetPropName, out string target) ? target : null) - .Where(t => t != null) - .ToArray(); - - bool hasiOSTargets = targets.Contains("ios-device"); - bool hastvOSTargets = targets.Contains("tvos-device"); - - if (hasiOSTargets) - { - DownloadProvisioningProfile(TargetPlatform.iOS); - } - - if (hastvOSTargets) - { - DownloadProvisioningProfile(TargetPlatform.tvOS); - } - } - - private void DownloadProvisioningProfile(TargetPlatform platform) - { - var targetFile = Path.Combine(TmpDir, GetProvisioningProfileFileName(platform)); - - using var client = new WebClient(); - _helpers.DirectoryMutexExec(async () => { - if (File.Exists(targetFile)) - { - Log.LogMessage($"Provisioning profile is already downloaded"); - return; - } - - Log.LogMessage($"Downloading {platform} provisioning profile to {targetFile}"); - - await client.DownloadFileTaskAsync(new Uri(GetProvisioningProfileUrl(platform)), targetFile); - }, TmpDir); - } } } diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.props b/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.props index 9a8018e210f..b6235a34b39 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.props +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.props @@ -9,11 +9,9 @@ - Helix + Helix - - diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.props b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.props index 1beaf8122f0..6f7148fea9d 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.props +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.props @@ -8,6 +8,6 @@ https://dnceng.pkgs.visualstudio.com/public/_packaging/dotnet-eng/nuget/v3/index.json - https://netcorenativeassets.blob.core.windows.net/resource-packages/external/macos/signing/NET_Apple_Development_{PLATFORM}.mobileprovision + https://netcorenativeassets.blob.core.windows.net/resource-packages/external/macos/signing/NET_Apple_Development_iOS.mobileprovision diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.targets b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.targets index 0e31001124d..8df9688ae1a 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.targets +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/XHarnessRunner.targets @@ -38,7 +38,7 @@ <_XHarnessExtraToolFiles Include="$(_XHarnessCliPath)\*.*" /> - + @@ -103,11 +103,18 @@ + + + + + ProvisioningProfilePath="@(_XHarnessProvisioningProfile)">