From f17bc64d1b6319e3fc04c19dcee2dfcd7e7438d3 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 16 Feb 2023 14:30:19 +0100 Subject: [PATCH] [msbuild/dotnet] Rework Hot Restart builds. Rework Hot Restart builds to use as much as possible of the normal build logic, because this is the easiest way to make sure the Hot Restart build is as close as possible to normal builds (and we don't end up missing features). This is done by executing selected parts of a normal build, and a the end we have a new task that computes where each file goes in the various output directories Hot Restart uses (HotRestartAppBundlePath, HotRestartContentDir, HotRestartAppContentDir, etc.) --- dotnet/targets/Xamarin.Shared.Sdk.targets | 9 +- msbuild/Xamarin.Shared/Xamarin.Shared.targets | 10 +- .../Tasks/ComputeHotRestartBundleContents.cs | 164 ++++++++++++ .../Xamarin.iOS.HotRestart.targets | 246 ++++++------------ .../Xamarin.iOS.Tasks.Windows.csproj | 3 + 5 files changed, 252 insertions(+), 180 deletions(-) create mode 100644 msbuild/Xamarin.iOS.Tasks.Windows/Tasks/ComputeHotRestartBundleContents.cs diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index a5308f6452..39d4d46d2d 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -1495,9 +1495,10 @@ + + + diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index c1cadeca00..e77024519b 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -450,9 +450,10 @@ Copyright (C) 2018 Microsoft. All rights reserved. + - + + diff --git a/msbuild/Xamarin.iOS.Tasks.Windows/Tasks/ComputeHotRestartBundleContents.cs b/msbuild/Xamarin.iOS.Tasks.Windows/Tasks/ComputeHotRestartBundleContents.cs new file mode 100644 index 0000000000..8271353c86 --- /dev/null +++ b/msbuild/Xamarin.iOS.Tasks.Windows/Tasks/ComputeHotRestartBundleContents.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +using Xamarin.MacDev.Tasks; + +#nullable enable + +namespace Xamarin.iOS.HotRestart.Tasks { + public class ComputeHotRestartBundleContents : Task { + #region Inputs + + [Required] + public string HotRestartAppContentDir { get; set; } = string.Empty; + + [Required] + public string HotRestartContentDir { get; set; } = string.Empty; + + [Required] + public string HotRestartContentStampDir { get; set; } = string.Empty; + + [Required] + public string HotRestartSignedAppDir { get; set; } = string.Empty; + + [Required] + public string RelativeAppBundlePath { get; set; } = string.Empty; + + [Required] + public string TargetFrameworkMoniker { get; set; } = string.Empty; + + [Required] + public ITaskItem [] ResolvedFileToPublish { get; set; } = Array.Empty (); + + #endregion + + #region Outputs + + [Output] + public ITaskItem [] HotRestartAppContentDirContents { get; set; } = Array.Empty (); + + [Output] + public ITaskItem [] HotRestartContentDirContents { get; set; } = Array.Empty (); + + [Output] + public ITaskItem [] HotRestartSignedAppDirContents { get; set; } = Array.Empty (); + + #endregion + + ITaskItem CopyWithDestinationAndStamp (ITaskItem item, string destinationDirectory, string? stampDirectory = null) + { + var rv = new TaskItem (item); + // The RelativePath metadata specifies the path of the item inside the app bundle relative to the output directory. + // We need to convert this to a path relative to the root of the app bundle, since we copy files to a different + // directory than the normal app bundle output path. + var relativePath = item.GetMetadata ("RelativePath"); + if (relativePath.StartsWith (RelativeAppBundlePath, StringComparison.OrdinalIgnoreCase)) + relativePath = relativePath.Substring (RelativeAppBundlePath.Length).TrimStart ('\\', '/'); + relativePath = relativePath.Replace ('/', Path.DirectorySeparatorChar); + // And here we compute the final absolute path of the item, into our own output directory. + rv.SetMetadata ("DestinationFile", Path.Combine (destinationDirectory, relativePath)); + // Also set a stamp file metadata if we're supposed to write a stamp file. + if (!string.IsNullOrEmpty (stampDirectory)) + rv.SetMetadata ("StampFile", Path.Combine (stampDirectory, relativePath)); + return rv; + } + + // The Copy task can't copy directories, so expand directories to their individual files + List ExpandDirectories (List items) + { + var rv = new List (); + + foreach (var item in items) { + if (File.Exists (item.ItemSpec)) { + rv.Add (item); + } else if (Directory.Exists (item.ItemSpec)) { + var entries = Directory.GetFileSystemEntries (item.ItemSpec).ToArray (); + Log.LogMessage (MessageImportance.Low, $"Expanding {item.ItemSpec} with {entries.Length} items:"); + foreach (var entry in entries) { + if (Directory.Exists (entry)) { + Log.LogMessage (MessageImportance.Low, $" Skipped directory: {entry}"); + continue; + } + var relativePathSuffix = entry.Substring (item.ItemSpec.Length).TrimStart ('\\', '/'); + var relativePath = Path.Combine (item.GetMetadata ("RelativePath"), relativePathSuffix); + var destinationFile = Path.Combine (item.GetMetadata ("DestinationFile"), relativePathSuffix); + var file = new TaskItem (item); + file.ItemSpec = entry; + file.SetMetadata ("RelativePath", relativePath); + file.SetMetadata ("DestinationFile", destinationFile); + rv.Add (file); + Log.LogMessage (MessageImportance.Low, $" Added {file.ItemSpec} with relative path: {relativePath} and destination file: {destinationFile}"); + } + } else { + // Trust that this will just somehow work. + rv.Add (item); + } + } + + return rv; + } + + public override bool Execute () + { + var appContentDirContents = new List (); + var contentDirContents = new List (); + var signedAppDirContents = new List (); + + foreach (var item in ResolvedFileToPublish) { + var publishFolderType = item.GetPublishFolderType (); + switch (publishFolderType) { + case PublishFolderType.RootDirectory: + case PublishFolderType.Assembly: + case PublishFolderType.Resource: + appContentDirContents.Add (CopyWithDestinationAndStamp (item, HotRestartAppContentDir)); + contentDirContents.Add (CopyWithDestinationAndStamp (item, HotRestartContentDir, HotRestartContentStampDir)); + break; + + case PublishFolderType.AppleFramework: + var filename = Path.GetFileName (item.ItemSpec); + var dirname = Path.GetFileName (Path.GetDirectoryName (item.ItemSpec)); + if (string.Equals (filename + ".framework", dirname, StringComparison.OrdinalIgnoreCase)) + item.ItemSpec = Path.GetDirectoryName (item.ItemSpec); + // These have to be signed + signedAppDirContents.Add (CopyWithDestinationAndStamp (item, HotRestartSignedAppDir)); + break; + case PublishFolderType.PlugIns: + case PublishFolderType.DynamicLibrary: + case PublishFolderType.PluginLibrary: + // These have to be signed + signedAppDirContents.Add (CopyWithDestinationAndStamp (item, HotRestartSignedAppDir)); + break; + + case PublishFolderType.Unset: // Don't copy unknown stuff anywhere + case PublishFolderType.None: // Don't copy unknown stuff anywhere + case PublishFolderType.Unknown: // Don't copy unknown stuff anywhere + case PublishFolderType.AppleBindingResourcePackage: // These aren't copied to the bundle + case PublishFolderType.CompressedAppleBindingResourcePackage: // These aren't copied to the bundle + case PublishFolderType.StaticLibrary: // These aren't copied to the bundle + case PublishFolderType.CompressedAppleFramework: // Shouldn't really happen? Should be uncompresed by the time we get here. + case PublishFolderType.CompressedPlugIns: // Shouldn't really happen? Should be uncompresed by the time we get here. + Log.LogMessage (MessageImportance.Low, $" Skipped {item.ItemSpec} because PublishFolderType={publishFolderType} items aren't copied to the app bundle."); + continue; + default: + Log.LogMessage (MessageImportance.Low, $" Skipped {item.ItemSpec} because of unknown PublishFolderType={publishFolderType}."); + continue; + } + } + + appContentDirContents = ExpandDirectories (appContentDirContents); + contentDirContents = ExpandDirectories (contentDirContents); + signedAppDirContents = ExpandDirectories (signedAppDirContents); + + HotRestartAppContentDirContents = appContentDirContents.ToArray (); + HotRestartContentDirContents = contentDirContents.ToArray (); + HotRestartSignedAppDirContents = signedAppDirContents.ToArray (); + + return !Log.HasLoggedErrors; + } + } +} diff --git a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.HotRestart.targets b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.HotRestart.targets index b11cd468f0..e57a354a58 100644 --- a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.HotRestart.targets +++ b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.HotRestart.targets @@ -1,10 +1,10 @@ - + @@ -52,97 +52,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_HotRestartFrameworks Include="@(None -> '%(RootDir)%(Directory)')" Condition="$([System.String]::new('%(Directory)').EndsWith('.framework\'))" KeepDuplicates="false" /> - <_HotRestartFrameworks Include="@(_UnpackedFramework);@(NativeReference)" KeepDuplicates="false" /> - <_HotRestartFrameworkExecutables Include="@(_HotRestartFrameworks -> '%(FullPath)\%(Filename)')" - Outputs="$(DeviceSpecificIntermediateOutputPath)DynamicFrameworks\%(Filename)%(Extension).stamp" KeepDuplicates="false" /> - - - - - <_CollectHotRestartDynamicFrameworksDependsOn> - _CollectHotRestartFrameworkAssemblies; - _UnpackHotRestartFrameworkAssemblies; - _CollectHotRestartFrameworks; - - - - - - - - - - - - - - - - - - <_HotRestartFrameworkFiles Include="%(_DynamicFrameworks.FullPath)\**\*.*" Condition="'%(_DynamicFrameworks.FullPath)' != ''" KeepDuplicates="false" > - %(FrameworkDir) - - + + + + + + + @@ -155,7 +79,7 @@ - + @@ -171,20 +95,6 @@ - - - <_FilesToHotRestartBundle Include="$(HotRestartAppBundlePath)\Extracted" /> - - <_FilesToHotRestartContent Include="@(MainAssembly);" > - - - - <_FilesToHotRestartContent Include="@(_FilesToHotRestartContent -> '%(RootDir)%(Directory)%(Filename).pdb')" - Condition="Exists('%(RootDir)%(Directory)%(Filename).pdb')" /> - - <_FilesToHotRestartContent Include="@(ReferenceCopyLocalPaths -> Distinct())" - Condition="Exists('$(HotRestartAppBundlePath)\%(Filename)%(Extension)') == 'false' And '%(Extension)' != '.a' And '%(Extension)' != '.dylib' And '%(Extension)' != '.dat'"/> - - + - - - + DependsOnTargets="_CreateHotRestartCachedBundle;_UnpackLibraryResources" + Inputs="@(_BundleResourceWithLogicalName)" + Outputs="@(_BundleResourceWithLogicalName -> '$(HotRestartSignedAppDir)%(LogicalName)')"> + - - - - - - - - - + - - - - - - + DependsOnTargets="_CreateHotRestartCachedBundle;_ComputeHotRestartBundleContents" + Inputs="@(_HotRestartSignedAppDirContents)" + Outputs="@(_HotRestartSignedAppDirContents -> '%(DestinationFile)')"> + - - - - - - - - - - - - - - - + + DependsOnTargets="_CreateHotRestartCachedBundle;_ComputeHotRestartBundleContents" + Inputs="@(_HotRestartContentDirContents)" + Outputs="@(_HotRestartContentDirContents -> '%(DestinationFile)')"> - - + + + + + + + + + + @@ -309,16 +204,21 @@ <_CodeSignHotRestartInputs Include="$(AppBundleManifest)" Outputs="$(HotRestartSignedAppDir)$([System.IO.Path]::GetFileName('$(AppBundleManifest)'))" /> <_CodeSignHotRestartInputs Include="$(CodesignEntitlements)" Outputs="$(HotRestartSignedAppDir)$(CodesignEntitlements)" /> <_CodeSignHotRestartInputs Include="$(_ProvisioningProfilePath)" Outputs="$(HotRestartSignedAppDir)embedded.mobileprovision" /> - <_CodeSignHotRestartInputs Include="@(_HotRestartFrameworkFiles)" Outputs="$(HotRestartAppBundlePath)\Frameworks\%(_HotRestartFrameworkFiles.FrameworkDir)%(RecursiveDir)%(Filename)%(Extension)" /> + <_CodeSignHotRestartInputs Include="@(_HotRestartSignedAppDirContents)" Outputs="%(_HotRestartSignedAppDirContents.DestinationFile)" /> <_CodeSignHotRestartInputs Include="$(HotRestartAppBundlePath)\Extracted" Outputs="$(HotRestartSignedAppDir)Extracted" /> <_CreateHotRestartOutputBundleDependsOn> + _CreateHotRestartCachedBundle; + _UnpackLibraryResources; + _ComputeHotRestartBundleContents; + _CopyHotRestartBundleResources; + _CopyFilesToHotRestartSignedAppDirContents; + _CopyFilesToHotRestartContentDir; + _CopyFilesToHotRestartAppContentDir; _CodesignHotRestartAppBundle; - _CopyFilesToHotRestartBundle; - _CopyFilesToHotRestartContent; @@ -327,7 +227,7 @@ diff --git a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.Tasks.Windows.csproj b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.Tasks.Windows.csproj index 3a70fd058e..2be9bfdc27 100644 --- a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.Tasks.Windows.csproj +++ b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.iOS.Tasks.Windows.csproj @@ -43,6 +43,9 @@ + + PublishFolderType.cs +