diff --git a/Make.config b/Make.config index 52a7e0a207a6..b89ea8a52749 100644 --- a/Make.config +++ b/Make.config @@ -140,8 +140,8 @@ MACCATALYST_NUGET_VERSION_FULL=$(MACCATALYST_NUGET_VERSION_NO_METADATA)+$(NUGET_ # Xcode version should have both a major and a minor version (even if the minor version is 0) XCODE_VERSION=13.0 -XCODE_URL=https://bosstoragemirror.azureedge.net/internal-files/xcodes/Xcode_13_beta.xip -XCODE_DEVELOPER_ROOT=/Applications/Xcode_13.0.0-beta.app/Contents/Developer +XCODE_URL=https://dl.internalx.com/internal-files/xcodes/Xcode_13_beta_2.xip +XCODE_DEVELOPER_ROOT=/Applications/Xcode_13.0.0-beta2.app/Contents/Developer XCODE_PRODUCT_BUILD_VERSION:=$(shell /usr/libexec/PlistBuddy -c 'Print :ProductBuildVersion' $(XCODE_DEVELOPER_ROOT)/../version.plist) # Mono version embedded in XI/XM (NEEDED_MONO_VERSION/BRANCH) are specified in mk/mono.mk diff --git a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets index b2bf6f85e06d..d73dba50929e 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets @@ -24,33 +24,45 @@ $(MtouchArch) - - - - ARM64 - x86_64 - i386 - ARMv7 - - - ARM64 - x86_64 - - - i386 - ARM64_32 - - - x86_64 - ARM64 + + + + <_RuntimeIdentifiersClashMessage>Both RuntimeIdentifier and RuntimeIdentifiers are set. The value for RuntimeIdentifier will be ignored. + + - - x86_64 - ARM64 + + + <_ComputeTargetArchitecturesDependsOn> + $(_ComputeTargetArchitecturesDependsOn); + _MapRuntimeIdentifierToTargetArchitecture + + + + + + + <_RuntimeIdentifierWithTargetArchitecture Include="$(RuntimeIdentifiers);$(RuntimeIdentifier)" /> + + <_RuntimeIdentifierWithTargetArchitecture Update="@(_RuntimeIdentifierWithTargetArchitecture)"> + ARM64 + ARMv7 + x86_64 + i386 + + <_RuntimeIdentifiersWithoutTargetArchitecture Include="@(_RuntimeIdentifierWithTargetArchitecture)" Condition="'%(_RuntimeIdentifierWithTargetArchitecture.TargetArchitecture)' == ''" /> + + + + + @(_RuntimeIdentifierWithTargetArchitecture -> '%(TargetArchitecture)', ', ') + + + - iPhoneSimulator + iPhoneSimulator iPhone diff --git a/dotnet/targets/Xamarin.Shared.Sdk.props b/dotnet/targets/Xamarin.Shared.Sdk.props index 55d1039cfa23..b2abed3bc3d8 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.props +++ b/dotnet/targets/Xamarin.Shared.Sdk.props @@ -26,4 +26,29 @@ false + + + + iossimulator-x64 + tvossimulator-x64 + osx-x64 + maccatalyst-x64 + + true + <_RuntimeIdentifierUsesAppHost>false + false + $(IntermediateOutputPath)$(RuntimeIdentifier)\ + $(OutputPath)$(RuntimeIdentifier)\ + diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index deda614cb116..ba6c710dd17a 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -9,6 +9,7 @@ + - + + _ComputePublishTrimmed; BuildOnlySettings; _CollectBundleResources; @@ -167,9 +173,30 @@ Codesign; + + + _ComputePublishTrimmed; + BuildOnlySettings; + _CollectBundleResources; + _PackLibraryResources; + _UnpackLibraryResources; + $(BuildDependsOn); + _CreateAppBundle; + + + + + _RunRidSpecificBuild; + _CompileEntitlements; + _CompileAppManifest; + _CreateMergedAppBundle; + Codesign; + + - + + _DetectAppManifest; _CopyResourcesToBundle; _CompileCoreMLModels; @@ -193,11 +220,18 @@ _CopyAppExtensionsToBundle; - + + _CreateDebugSettings; _CreateDebugConfiguration; $(CreateAppBundleDependsOn); + + + + _CompileEntitlements; + _CreateMergedAppBundle; + @@ -209,14 +243,101 @@ - true + true + + + + + + + + <_AssemblyPublishDirectory Include="$(_AppBundlePath)"> + $(RuntimeIdentifier) + $(TargetArchitectures) + + + + + + + + <_RuntimeIdentifiersAsItems Include="$(RuntimeIdentifiers)" Condition=" '$(RuntimeIdentifiers)' != '' " /> + <_RuntimeIdentifiersAsItems Update="@(_RuntimeIdentifiersAsItems)"> + + $([System.String]::new('%(Identity)').Substring(0, $([System.String]::new('%(Identity)').IndexOf ('-')))) + + + + <_RuntimeIdentifierPlatforms Include="@(_RuntimeIdentifiersAsItems -> '%(Platform)')" /> + <_RuntimeIdentifierDistinctPlatforms Include="@(_RuntimeIdentifierPlatforms->Distinct())" /> + + + + + + + + + + + + + <_AssemblyPublishDirectories> + .xamarin/%(RuntimeIdentifier) + + <_AssemblyPublishInputs Include="@(_AssemblyPublishDirectories -> '%(Identity)/**')" /> + + + + + + + <_ArchitectureSpecificFiles Include="$(_RuntimeConfigurationFile)" Condition="'$(GenerateRuntimeConfigurationFiles)' != ''" /> + + <_ArchitectureSpecificFiles Include="$(_GlobalizationDataFile)" Condition="'$(_GlobalizationDataFile)' != ''" /> + + + + + + + + + + + + <_CustomLinkerOptionsFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)custom-linker-options.txt')) @@ -241,6 +362,7 @@ DeploymentTarget=$(_MinimumOSVersion) @(_BundlerEnvironmentVariables -> 'EnvironmentVariable=%(Identity)=%(Value)') @(_XamarinFrameworkAssemblies -> 'FrameworkAssembly=%(Filename)') + GlobalizationDataFile=$(_GlobalizationDataFile) Interpreter=$(MtouchInterpreter) IntermediateLinkDir=$(IntermediateLinkDir) InvariantGlobalization=$(InvariantGlobalization) @@ -493,7 +615,18 @@ - + + <_ComputeVariablesDependsOn>_GenerateBundleName;_ComputeFrameworkVariables + + <_ComputeVariablesDependsOn Condition="'$(RuntimeIdentifiers)' == ''">$(_ComputeVariablesDependsOn);ComputeResolvedFilesToPublishList + + + <_IntermediateNativeLibraryDir>$(IntermediateOutputPath)nativelibraries/ <_NativeExecutableName>$(_AppBundleName) @@ -780,13 +913,25 @@ + Condition="'$(_PlatformName)' != 'macOS' And '$(InvariantGlobalization)' != 'true' And '%(Filename)%(Extension)' == '$(_GlobalizationDataFile)'" /> + + + <_IsInvalidRuntimeIdentifier Condition="'$(_PlatformName)' == 'iOS' And '$(RuntimeIdentifier)' != 'iossimulator-x64' And '$(RuntimeIdentifier)' != 'iossimulator-x86' And '$(RuntimeIdentifier)' != 'ios-arm64' And '$(RuntimeIdentifier)' != 'ios-arm' ">true + <_IsInvalidRuntimeIdentifier Condition="'$(_PlatformName)' == 'tvOS' And '$(RuntimeIdentifier)' != 'tvossimulator-x64' And '$(RuntimeIdentifier)' != 'tvos-arm64'">true + <_IsInvalidRuntimeIdentifier Condition="'$(_PlatformName)' == 'macOS' And '$(RuntimeIdentifier)' != 'osx-x64' And '$(RuntimeIdentifier)' != 'osx-arm64'">true + <_IsInvalidRuntimeIdentifier Condition="'$(_PlatformName)' == 'MacCatalyst' And '$(RuntimeIdentifier)' != 'maccatalyst-x64' And '$(RuntimeIdentifier)' != 'maccatalyst-arm64'">true + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 09603c87c1d1..c9e432e7f3e2 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/installer - d67903215a7bca7802460f3ba8af1db4c64ef1f4 + e8b3b6bea1e37086869ba9aeafe65caa298537e7 https://github.com/mono/linker diff --git a/eng/Versions.props b/eng/Versions.props index 2d6f02030f91..654ed26f45e5 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,7 +1,7 @@ - 6.0.100-preview.7.21326.4 + 6.0.100-preview.7.21327.2 6.0.100-preview.6.21322.1 6.0.0-beta.21212.6 diff --git a/msbuild/Directory.Build.props b/msbuild/Directory.Build.props index 739e24a4d283..68d770a89634 100644 --- a/msbuild/Directory.Build.props +++ b/msbuild/Directory.Build.props @@ -1,5 +1,5 @@ - 1.3.24 + 1.3.26 diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs index b0a8a2fdb016..056ba8736032 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs @@ -1813,6 +1813,78 @@ public static string E7072 { } } + /// + /// Looks up a localized string similar to At least one app bundle must be specified.. + /// + public static string E7073 { + get { + return ResourceManager.GetString("E7073", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The app bundle {0} does not exist.. + /// + public static string E7074 { + get { + return ResourceManager.GetString("E7074", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No 'SpecificSubDirectory' metadata was provided for the app bundle {0}.. + /// + public static string E7075 { + get { + return ResourceManager.GetString("E7075", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't merge the symlink '{0}', it has different targets.. + /// + public static string E7076 { + get { + return ResourceManager.GetString("E7076", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to merge the file '{0}', it's different between the input app bundles.. + /// + public static string E7077 { + get { + return ResourceManager.GetString("E7077", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid app bundle: the Mach-O file {0} has dependent files.. + /// + public static string E7078 { + get { + return ResourceManager.GetString("E7078", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid app bundle: the file {0} has different types between the input app bundles.. + /// + public static string E7079 { + get { + return ResourceManager.GetString("E7079", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to App bundle file #{0}: {1}. + /// + public static string E7080 { + get { + return ResourceManager.GetString("E7080", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid framework: {0}. /// diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index f6ffc6b5543b..71a23a1d40f3 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1259,4 +1259,41 @@ Package product requirement file contains architectures ({0}) which mismatches with target architectures ({1}) + + + At least one app bundle must be specified. + + + + The app bundle {0} does not exist. + + + + No 'SpecificSubDirectory' metadata was provided for the app bundle {0}. + + + + Can't merge the symlink '{0}', it has different targets. + + + + Unable to merge the file '{0}', it's different between the input app bundles. + + + + Invalid app bundle: the Mach-O file {0} has dependent files. + + + + Invalid app bundle: the file {0} has different types between the input app bundles. + + + + App bundle file #{0}: {1} + + This error will be shown multiple times, listing the full path to the files causing E7076, E7077 and E7079. + {0}: a number indicating which input app bundle the file is from + {1}: the full path to the file in question + + diff --git a/msbuild/Xamarin.MacDev.Tasks.Core/ErrorHelper.msbuild.cs b/msbuild/Xamarin.MacDev.Tasks.Core/ErrorHelper.msbuild.cs new file mode 100644 index 000000000000..1ed9c7221dcc --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks.Core/ErrorHelper.msbuild.cs @@ -0,0 +1,53 @@ +// Copyright 2021, Microsoft Corp. All rights reserved, + +using System.Collections.Generic; + +using Xamarin.Utils; + +namespace Xamarin.Bundler { + public static partial class ErrorHelper { + public static ApplePlatform Platform; + + internal static string Prefix { + get { + return Xamarin.MacDev.Tasks.LoggingExtensions.ErrorPrefix; + } + } + + public enum WarningLevel { + Error = -1, + Warning = 0, + Disable = 1, + } + + static Dictionary warning_levels; + + public static WarningLevel GetWarningLevel (int code) + { + WarningLevel level; + + if (warning_levels == null) + return WarningLevel.Warning; + + // code -1: all codes + if (warning_levels.TryGetValue (-1, out level)) + return level; + + if (warning_levels.TryGetValue (code, out level)) + return level; + + return WarningLevel.Warning; + } + + public static void SetWarningLevel (WarningLevel level, int? code = null /* if null, apply to all warnings */) + { + if (warning_levels == null) + warning_levels = new Dictionary (); + if (code.HasValue) { + warning_levels [code.Value] = level; + } else { + warning_levels [-1] = level; // code -1: all codes. + } + } + } +} diff --git a/msbuild/Xamarin.MacDev.Tasks.Core/LoggingExtensions.cs b/msbuild/Xamarin.MacDev.Tasks.Core/LoggingExtensions.cs index b61fa7a9a080..1d09b67e09c4 100644 --- a/msbuild/Xamarin.MacDev.Tasks.Core/LoggingExtensions.cs +++ b/msbuild/Xamarin.MacDev.Tasks.Core/LoggingExtensions.cs @@ -6,7 +6,7 @@ namespace Xamarin.MacDev.Tasks public static class LoggingExtensions { const MessageImportance TaskPropertyImportance = MessageImportance.Normal; - static readonly string ErrorPrefix; + internal static readonly string ErrorPrefix; static LoggingExtensions () { diff --git a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs new file mode 100644 index 000000000000..5ab7fd6f1916 --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs @@ -0,0 +1,469 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; + +using Microsoft.Build.Framework; + +using Xamarin.Bundler; +using Xamarin.Localization.MSBuild; +using Xamarin.Utils; + +namespace Xamarin.MacDev.Tasks { + // This task will take two or more app bundles and merge them into a universal/fat app bundle. + // It will go through every file from the input app bundles and copy them to the output app bundle. + // + // If a file exists in more than one input app bundle, then the behavior depends on the file type: + // + // 1) MachO files are lipo'ed into a fat MachO file. + // 2) Managed assemblies (*.dll, *.exe) and their related files (satellite assemblies, app config, debug files, etc). are put into an + // RuntimeIdentifier-specific subdirectory. Our runtime knows how to locate assemblies in this RuntimeIdentifier-specific directory. + // 3) Other files that behave like managed assemblies (i.e. should be put into the architecture-specific subdirectory) + // are put there. These files are listed in the 'ArchitectureSpecificFiles' parameter. + // 4) Directories are copied as is, since they can't have different content. + // 5) If symlinks point to different files, an error is raised. + // 6) Any other files will cause errors to be raised. + public abstract partial class MergeAppBundlesTaskBase : XamarinTask { + + #region Inputs + // This is a list of files (filename only, no path, will match any file with the given name in the app bundle) + // that can be put in a RID-specific subdirectory. + public ITaskItem[] ArchitectureSpecificFiles { get; set; } + + // This is a list of files (filename only, no path, will match any file with the given name in the app bundle) + // to ignore/skip. + public ITaskItem [] IgnoreFiles { get; set; } + + // A list of the .app bundles to merge + [Required] + public ITaskItem [] InputAppBundles { get; set; } + + // The output app bundle + [Required] + public string OutputAppBundle { get; set; } + + [Required] + public string SdkDevPath { get; set; } + + #endregion + + enum FileType { + MachO, + PEAssembly, + ArchitectureSpecific, + Directory, + Symlink, + Other, + } + + class Entries : List { + public string BundlePath; + public string SpecificSubdirectory; + } + + class Entry { + public MergeAppBundlesTaskBase Task; + public Entries AppBundle; + public string RelativePath; + public FileType Type; + public List DependentFiles; + + public string FullPath => Path.Combine (AppBundle.BundlePath, RelativePath); + + void FindDependentFiles (Func condition) + { + var dependentFiles = AppBundle.Where (v => v != this).Where (condition).ToArray (); + + if (dependentFiles.Length > 0) { + if (DependentFiles == null) + DependentFiles = new List (); + + foreach (var dependentFile in dependentFiles) { + AppBundle.Remove (dependentFile); + DependentFiles.Add (dependentFile); + } + } + } + + public void FindDependentFiles () + { + // pdb + FindDependentFiles (v => string.Equals (v.RelativePath, Path.ChangeExtension (RelativePath, "pdb"), StringComparison.OrdinalIgnoreCase)); + + // config + FindDependentFiles (v => string.Equals (v.RelativePath, RelativePath + ".config", StringComparison.OrdinalIgnoreCase)); + + // satellite assemblies + var satelliteName = Path.GetFileNameWithoutExtension (RelativePath) + ".resources.dll"; + FindDependentFiles (v => { + if (v.Type != FileType.PEAssembly) + return false; + + // if the name isn't the satellite name, it's not a dependent assembly of ours + if (!string.Equals (Path.GetFileName (v.RelativePath), satelliteName, StringComparison.OrdinalIgnoreCase)) + return false; + + // if it's not in an immediate subdirectory, it's not a dependent assembly of ours + if (!string.Equals (Path.GetDirectoryName (Path.GetDirectoryName (v.RelativePath)), Path.GetDirectoryName (RelativePath), StringComparison.OrdinalIgnoreCase)) + return false; + + // if the name of the immediate subdirectory isn't a valid culture, then it's not a dependent assembly of ours + var immediateSubDir = Path.GetFileName (Path.GetDirectoryName (v.RelativePath)); + var cultureInfo = CultureInfo.GetCultureInfo (immediateSubDir); + if (cultureInfo == null) + return false; + + return true; + }); + + // also add the directories where the satellite assemblies are + if (DependentFiles?.Any () == true) { + FindDependentFiles (v => { + if (v.Type != FileType.Directory && v.Type != FileType.Symlink) + return false; + + return DependentFiles.Any (df => { + if (df.Type != FileType.PEAssembly) + return false; + + if (Path.GetDirectoryName (df.RelativePath) != v.RelativePath) + return false; + + return true; + }); + }); + } + } + + // Compare two entries. The entry type must be identical, and the comparison is otherwise specific to each entry type. + public bool IsIdenticalTo (Entry other) + { + if (other is null) + throw new ArgumentNullException (nameof (other)); + + // If they're of different types, they're really different. + if (other.Type != Type) + return false; + + // Directories can't be different + if (Type == FileType.Directory) + return true; + + // Symlinks are different if they point to different locations + if (Type == FileType.Symlink) { + var thisTarget = PathUtils.GetSymlinkTarget (FullPath); + var otherTarget = PathUtils.GetSymlinkTarget (other.FullPath); + return string.Equals (thisTarget, otherTarget, StringComparison.Ordinal); + } + + // Finally compare the contents of the files to determine equality. + if (!FileUtils.CompareFiles (FullPath, other.FullPath)) + return false; + + // If the entries have dependent files, we must consider them as well, so that + // the main file and all the dependent files are considered a single entity for + // the purpose of determining equality + if (DependentFiles != null && other.DependentFiles != null) { + // check if there are different number of dependent files, if so, we're different + if (DependentFiles.Count != other.DependentFiles.Count) + return false; + + // group by relative path + var grouped = DependentFiles.Union (other.DependentFiles).GroupBy (v => v.RelativePath); + foreach (var group in grouped) { + // the files don't match up (same number of files, but not the same filenames) + var files = group.ToArray (); + if (files.Length != 2) + return false; + + // compare the dependent files. + if (!files [0].IsIdenticalTo (files [1])) + return false; + } + } + + return true; + } + + public void CopyTo (string outputDirectory, string subDirectory = null) + { + string outputFile; + + if (subDirectory == null) { + outputFile = Path.Combine (outputDirectory, RelativePath); + } else { + var relativeAppDir = Path.GetDirectoryName (RelativePath); + if (string.IsNullOrEmpty (relativeAppDir)) { + outputFile = Path.Combine (outputDirectory, subDirectory, RelativePath); + } else { + outputFile = Path.Combine (outputDirectory, relativeAppDir, subDirectory, Path.GetFileName (RelativePath)); + } + } + + if (Type == FileType.Directory) { + Directory.CreateDirectory (outputFile); + } else if (Type == FileType.Symlink) { + Directory.CreateDirectory (Path.GetDirectoryName (outputFile)); + var symlinkTarget = PathUtils.GetSymlinkTarget (FullPath); + if (File.Exists (outputFile) && PathUtils.IsSymlink (outputFile) && PathUtils.GetSymlinkTarget (outputFile) == symlinkTarget) { + Task.Log.LogMessage (MessageImportance.Low, "Target '{0}' is up-to-date", outputFile); + } else { + PathUtils.FileDelete (outputFile); + PathUtils.Symlink (symlinkTarget, outputFile); + } + } else { + Directory.CreateDirectory (Path.GetDirectoryName (outputFile)); + if (!FileCopier.IsUptodate (FullPath, outputFile)) + File.Copy (FullPath, outputFile, true); + } + + if (DependentFiles != null) { + foreach (var file in DependentFiles) + file.CopyTo (outputDirectory, subDirectory); + } + } + } + + public override bool Execute () + { + if (InputAppBundles.Length == 0) { + Log.LogError (MSBStrings.E7073 /* At least one app bundle must be specified. */); + return false; + } + + // If we only have a single input directory, then we can just copy that as-is + if (InputAppBundles.Length == 1) { + var sourceDirectory = Path.GetFullPath (InputAppBundles [0].ItemSpec); + var targetDirectory = Path.GetFullPath (OutputAppBundle); + + // Make sure we have a trailing directory, so that UpdateDirectory copies the directory contents of the source directory. + if (sourceDirectory [sourceDirectory.Length - 1] != Path.DirectorySeparatorChar) + sourceDirectory += Path.DirectorySeparatorChar; + + Log.LogMessage (MessageImportance.Low, $"Copying the single input directory {sourceDirectory} to {targetDirectory}"); + FileCopier.UpdateDirectory (sourceDirectory, targetDirectory); + return !Log.HasLoggedErrors; + } + + if (!MergeAppBundles ()) + return false; + + return !Log.HasLoggedErrors; + } + + bool MergeAppBundles () + { + // Some validation + foreach (var input in InputAppBundles) { + if (!Directory.Exists (input.ItemSpec)) { + Log.LogError (MSBStrings.E7074 /* "The app bundle {0} does not exist." */, input.ItemSpec); + return false; + } + var specificSubdirectory = input.GetMetadata ("SpecificSubdirectory"); + if (string.IsNullOrEmpty (specificSubdirectory)) { + Log.LogError (MSBStrings.E7075 /* No 'SpecificSubDirectory' metadata was provided for the app bundle {0}. */, input.ItemSpec); + return false; + } + } + + // Gather all the files in each input app bundle + var inputFiles = new Entries [InputAppBundles.Length]; + for (var i = 0; i < InputAppBundles.Length; i++) { + var input = InputAppBundles [i]; + var specificSubdirectory = input.GetMetadata ("SpecificSubdirectory"); + var fullInput = Path.GetFullPath (input.ItemSpec); + // strip the trailing path separator + if (fullInput[fullInput.Length - 1] == Path.DirectorySeparatorChar) + fullInput = fullInput.Substring (0, fullInput.Length - 1); + // get all the files and subdirectories in the input app bundle + var files = Directory.GetFileSystemEntries (fullInput, "*", SearchOption.AllDirectories); + var entries = new Entries () { + BundlePath = fullInput, + SpecificSubdirectory = specificSubdirectory, + }; + foreach (var file in files) { + var relativePath = file.Substring (fullInput.Length + 1); + var entry = new Entry { + Task = this, + RelativePath = relativePath, + AppBundle = entries, + Type = GetFileType (file), + }; + entries.Add (entry); + } + inputFiles [i] = entries; + } + + // Group dependent files for assemblies + for (var i = 0; i < inputFiles.Length; i++) { + var list = inputFiles [i]; + var assemblies = list.Where (v => v.Type == FileType.PEAssembly).ToArray (); + foreach (var assembly in assemblies) { + assembly.FindDependentFiles (); + } + } + + // List the input + foreach (var list in inputFiles) { + Log.LogMessage (MessageImportance.Low, $"Input files found in {list.BundlePath}:"); + foreach (var file in list) { + Log.LogMessage (MessageImportance.Low, $" {file.RelativePath} Type: {file.Type} Dependent files: {file.DependentFiles?.Count.ToString () ?? "0"}"); + if (file.DependentFiles?.Any () == true) { + foreach (var df in file.DependentFiles) { + Log.LogMessage (MessageImportance.Low, $" {df.RelativePath} Type: {df.Type}"); + } + } + } + } + + // Group the input by relative path in the output app bundle + var map = new Dictionary> (); + foreach (var list in inputFiles) { + foreach (var file in list) { + if (!map.TryGetValue (file.RelativePath, out var groupedList)) { + map [file.RelativePath] = groupedList = new List (); + } + groupedList.Add (file); + } + } + + // Remove any ignored files + if (IgnoreFiles != null && IgnoreFiles.Length > 0) { + foreach (var spec in IgnoreFiles) { + var file = spec.ItemSpec; + if (map.Remove (file)) { + Log.LogMessage (MessageImportance.Low, "Ignored the file '{0}'", file); + } else { + Log.LogMessage (MessageImportance.Normal, "Asked to ignore the file '{0}', but no such file was found in any of the input app bundles.", file); + } + } + } + + // Verify that the type of the input for each target file is the same + foreach (var kvp in map) { + var types = kvp.Value.Select (v => v.Type).Distinct (); + if (types.Count () > 1) { + // Files of different types. + Log.LogError (MSBStrings.E7079 /* Invalid app bundle: the file {0} has different types between the input app bundles. */, kvp.Value.First ().RelativePath); + ListFiles (kvp.Value); + return false; + } + } + + // Merge stuff + Directory.CreateDirectory (OutputAppBundle); + foreach (var kvp in map) { + var relativePath = kvp.Key; + var entries = kvp.Value; + var outputFile = Path.Combine (OutputAppBundle, relativePath); + + if (entries.Count == 1) { + // just copy the file(s) if there's only one + Log.LogMessage (MessageImportance.Low, $"The file '{entries [0].RelativePath}' only exists in '{entries [0].AppBundle.BundlePath}' and will be copied as-is to the merged app bundle."); + entries [0].CopyTo (OutputAppBundle); + continue; + } + + // If they're all the same, just copy the first one + var identical = true; + for (var i = 1; i < entries.Count; i++) { + if (!entries [0].IsIdenticalTo (entries [i])) { + identical = false; + break; + } + } + if (identical) { + // All the input files are identical. Just copy the first one into the bundle. + Log.LogMessage (MessageImportance.Low, $"All the files for '{entries [0].RelativePath}' are identical between all the input app bundles."); + entries [0].CopyTo (OutputAppBundle); + continue; + } + + // Custom merging is needed, depending on the type + switch (entries [0].Type) { + case FileType.MachO: + MergeMachOFiles (outputFile, entries); + break; + case FileType.PEAssembly: + case FileType.ArchitectureSpecific: + MergeArchitectureSpecific (entries); + break; + case FileType.Symlink: + Log.LogError (MSBStrings.E7076 /* Can't merge the symlink '{0}', it has different targets */, entries [0].RelativePath); + ListFiles (entries); + break; + default: + Log.LogError (MSBStrings.E7077 /* Unable to merge the file '{0}', it's different between the input app bundles. */, entries [0].RelativePath); + ListFiles (entries); + break; + } + } + + return !Log.HasLoggedErrors; + } + + void ListFiles (List entries) + { + for (var i = 0; i < entries.Count; i++) { + Log.LogError (MSBStrings.E7080 /* App bundle file #{0}: {1} */, i + 1, entries [i].FullPath); + } + } + + void MergeArchitectureSpecific (IList inputs) + { + foreach (var input in inputs) { + Log.LogMessage (MessageImportance.Low, $"Copying '{input.RelativePath}' to the specific subdirectory {input.AppBundle.SpecificSubdirectory} for the merged app bundle."); + input.CopyTo (OutputAppBundle, input.AppBundle.SpecificSubdirectory); + } + } + + void MergeMachOFiles (string output, IList input) + { + if (input.Any (v => v.DependentFiles?.Any () == true)) { + Log.LogError (MSBStrings.E7078 /* Invalid app bundle: the Mach-O file {0} has dependent files. */, input.First ().RelativePath); + return; + } + + var sourceFiles = input.Select (v => v.FullPath).ToArray (); + + if (FileCopier.IsUptodate (sourceFiles, new string [] { output })) + return; + + Log.LogMessage (MessageImportance.Low, $"Lipoing '{input [0].RelativePath}' for the merged app bundle from the following sources:\n\t{string.Join ("\n\t", input.Select (v => v.FullPath))}"); + + var arguments = new List (); + arguments.Add ("-create"); + arguments.Add ("-output"); + arguments.Add (output); + arguments.AddRange (sourceFiles); + ExecuteAsync ("lipo", arguments, sdkDevPath: SdkDevPath).Wait (); + } + + FileType GetFileType (string path) + { + if (Directory.Exists (path)) + return FileType.Directory; + + if (PathUtils.IsSymlink (path)) + return FileType.Symlink; + + if (path.EndsWith (".exe", StringComparison.Ordinal) || path.EndsWith (".dll", StringComparison.Ordinal)) + return FileType.PEAssembly; + + if (MachO.IsMachOFile (path)) + return FileType.MachO; + + if (StaticLibrary.IsStaticLibrary (path)) + return FileType.MachO; + + if (ArchitectureSpecificFiles != null) { + var filename = Path.GetFileName (path); + if (ArchitectureSpecificFiles.Any (v => v.ItemSpec == filename)) + return FileType.ArchitectureSpecific; + } + + return FileType.Other; + } + } +} diff --git a/msbuild/Xamarin.MacDev.Tasks.Core/Xamarin.MacDev.Tasks.Core.csproj b/msbuild/Xamarin.MacDev.Tasks.Core/Xamarin.MacDev.Tasks.Core.csproj index 15036df3b7c1..db87ab060296 100644 --- a/msbuild/Xamarin.MacDev.Tasks.Core/Xamarin.MacDev.Tasks.Core.csproj +++ b/msbuild/Xamarin.MacDev.Tasks.Core/Xamarin.MacDev.Tasks.Core.csproj @@ -5,6 +5,7 @@ false true latest + $(DefineConstants);MSBUILD_TASKS @@ -45,6 +46,21 @@ PListExtensions.cs + + MachO.cs + + + error.cs + + + ErrorHelper.cs + + + external\RuntimeException.cs + + + external\FileUtils.cs + diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs new file mode 100644 index 000000000000..0e97095b94bd --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs @@ -0,0 +1,20 @@ +using Xamarin.Messaging.Build.Client; + +namespace Xamarin.MacDev.Tasks { + public class MergeAppBundles : MergeAppBundlesTaskBase { + public override bool Execute () + { + if (!string.IsNullOrEmpty (SessionId)) + return new TaskRunner (SessionId, BuildEngine4).RunAsync (this).Result; + + return base.Execute (); + } + + public void Cancel () + { + if (!string.IsNullOrEmpty (SessionId)) + BuildConnection.CancelAsync (SessionId, BuildEngine4).Wait (); + } + } +} + diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.props b/msbuild/Xamarin.Shared/Xamarin.Shared.props index e2c70d16fd8a..5d71d42eb25a 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.props +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.props @@ -132,7 +132,9 @@ Copyright (C) 2020 Microsoft. All rights reserved. <_SpecifiedCodesignKey Condition="'$(_PlatformName)' == 'macOS'">$(CodeSigningKey) <_SpecifiedCodesignKey Condition="'$(_PlatformName)' != 'macOS'">$(CodesignKey) + + $(XamMacArch) $(MtouchArch) @@ -143,7 +145,9 @@ Copyright (C) 2020 Microsoft. All rights reserved. ARMv7k ARM64 ARMv7 + + <_BundlerDebug Condition="'$(_BundlerDebug)' == '' And '$(_PlatformName)' == 'macOS'">$(MmpDebug) diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index f4170544a0cb..2c3b8dea923b 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -280,6 +280,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. _GenerateBundleName; _DetectSigningIdentity; _ComputeTargetFrameworkMoniker; + _ComputeTargetArchitectures; @@ -593,7 +594,14 @@ Copyright (C) 2018 Microsoft. All rights reserved. - + + <_ComputeTargetArchitecturesDependsOn> + $(_ComputeTargetArchitecturesDependsOn); + _ComputeTargetFrameworkMoniker; + + + + + diff --git a/tests/common/TestProjects/ComplexAssembly/ComplexAssembly.cs b/tests/common/TestProjects/ComplexAssembly/ComplexAssembly.cs new file mode 100644 index 000000000000..8bd4e7672a36 --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/ComplexAssembly.cs @@ -0,0 +1,13 @@ +// +// Now you see me, now you don't +// +// Authors: +// Rolf Bjarne Kvinge (rolf@xamarin.com) +// +// Copyright 2021 Microsoft Corp. All rights reserved. +// + +namespace ComplexAssembly { + public class ItIsJustImaginary { + } +} diff --git a/tests/common/TestProjects/ComplexAssembly/ComplexAssembly.csproj b/tests/common/TestProjects/ComplexAssembly/ComplexAssembly.csproj new file mode 100644 index 000000000000..e1efb1eec0fa --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/ComplexAssembly.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + latest + Library + + + portable + + $(MSBuildThisFileDirectory)\.. + $(RootTestsDirectory)\ComplexAssembly + + + App.config + + diff --git a/tests/common/TestProjects/ComplexAssembly/Makefile b/tests/common/TestProjects/ComplexAssembly/Makefile new file mode 100644 index 000000000000..da40ab9516fa --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/Makefile @@ -0,0 +1,12 @@ +TOP=../../../.. + +include $(TOP)/Make.config + +NuGet%config global%json: + $(Q) $(MAKE) -C $(TOP)/tests/dotnet all + $(Q) $(CP) $(TOP)/tests/dotnet/global.json $(TOP)/tests/dotnet/NuGet.config . + +.build-stamp.binlog: Makefile $(wildcard *.cs) $(wildcard *.csproj) $(wildcard *.resx) $(wildcard *.config) NuGet.config global.json + $(Q) $(DOTNET6) build *.csproj $(MSBUILD_VERBOSITY) /bl:$@ + +all-local:: .build-stamp.binlog diff --git a/tests/common/TestProjects/ComplexAssembly/README.md b/tests/common/TestProjects/ComplexAssembly/README.md new file mode 100644 index 000000000000..587764f2eea5 --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/README.md @@ -0,0 +1,10 @@ +# ComplexAssembly + +This is a project that produces: + +* An assembly +* The assembly has a debug file (pdb). +* The assembly as a .config file. +* The assembly has satellite assemblies. + +So: a complex assembly. diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.de.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.de.resx new file mode 100755 index 000000000000..92ad79e63db6 --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/Welcome.de.resx @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Willkommen + + + diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.en-AU.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.en-AU.resx new file mode 100755 index 000000000000..3caa45b14a9b --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/Welcome.en-AU.resx @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + G'day + + + diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.es.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.es.resx new file mode 100755 index 000000000000..640df897c8cc --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/Welcome.es.resx @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bienvenido + + + diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.resx new file mode 100755 index 000000000000..1c8ba5b2e631 --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/Welcome.resx @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Welcome + + + diff --git a/tests/dotnet/MyCocoaApp/MyCocoaApp.csproj b/tests/dotnet/MyCocoaApp/MyCocoaApp.csproj index 6e701ae12bc3..d9b70c3409b9 100644 --- a/tests/dotnet/MyCocoaApp/MyCocoaApp.csproj +++ b/tests/dotnet/MyCocoaApp/MyCocoaApp.csproj @@ -2,7 +2,6 @@ net6.0-macos - osx-x64 Exe \ No newline at end of file diff --git a/tests/dotnet/MySimpleApp/AppDelegate.cs b/tests/dotnet/MySimpleApp/AppDelegate.cs new file mode 100644 index 000000000000..72f23d2d352e --- /dev/null +++ b/tests/dotnet/MySimpleApp/AppDelegate.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.InteropServices; + +using Foundation; + +namespace MySimpleApp +{ + public class Program + { + static int Main (string[] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + + Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + + return 0; + } + } +} diff --git a/tests/dotnet/MySimpleApp/MacCatalyst/Info.plist b/tests/dotnet/MySimpleApp/MacCatalyst/Info.plist new file mode 100644 index 000000000000..6631ffa6f242 --- /dev/null +++ b/tests/dotnet/MySimpleApp/MacCatalyst/Info.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/MySimpleApp/MacCatalyst/Makefile b/tests/dotnet/MySimpleApp/MacCatalyst/Makefile new file mode 100644 index 000000000000..5a1e3a910eec --- /dev/null +++ b/tests/dotnet/MySimpleApp/MacCatalyst/Makefile @@ -0,0 +1,9 @@ +TOP=../../../.. + +include $(TOP)/Make.config + +build: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) + +run: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) -t:Run diff --git a/tests/dotnet/MySimpleApp/MacCatalyst/MySimpleApp.csproj b/tests/dotnet/MySimpleApp/MacCatalyst/MySimpleApp.csproj new file mode 100644 index 000000000000..ccebf1f77846 --- /dev/null +++ b/tests/dotnet/MySimpleApp/MacCatalyst/MySimpleApp.csproj @@ -0,0 +1,7 @@ + + + + net6.0-maccatalyst + + + diff --git a/tests/dotnet/MySimpleApp/Makefile b/tests/dotnet/MySimpleApp/Makefile new file mode 100644 index 000000000000..efb2400edc0f --- /dev/null +++ b/tests/dotnet/MySimpleApp/Makefile @@ -0,0 +1,20 @@ +TOP=../../.. + +include $(TOP)/Make.config + +prepare: + cd .. && $(MAKE) global.json NuGet.config + rm -Rf */bin */obj + +all-ios: prepare + $(DOTNET6) build iOS/*.csproj /bl + +all-mac: prepare + $(DOTNET6) build macOS/*.csproj /bl + +run-mac: + ./macOS/bin/Debug/net6.0-macos/osx-x64/$(notdir $(CURDIR)).app/Contents/MacOS/$(notdir $(CURDIR)) + +diag: + cd .. && $(MAKE) global.json NuGet.config + $(DOTNET6) build /v:diag *binlog diff --git a/tests/dotnet/MySimpleApp/iOS/Info.plist b/tests/dotnet/MySimpleApp/iOS/Info.plist new file mode 100644 index 000000000000..4cbda4223702 --- /dev/null +++ b/tests/dotnet/MySimpleApp/iOS/Info.plist @@ -0,0 +1,8 @@ + + + + + + MinimumOSVersion + 10.0 + diff --git a/tests/dotnet/MySimpleApp/iOS/Makefile b/tests/dotnet/MySimpleApp/iOS/Makefile new file mode 100644 index 000000000000..5a1e3a910eec --- /dev/null +++ b/tests/dotnet/MySimpleApp/iOS/Makefile @@ -0,0 +1,9 @@ +TOP=../../../.. + +include $(TOP)/Make.config + +build: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) + +run: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) -t:Run diff --git a/tests/dotnet/MySimpleApp/iOS/MySimpleApp.csproj b/tests/dotnet/MySimpleApp/iOS/MySimpleApp.csproj new file mode 100644 index 000000000000..bbb942faa1a6 --- /dev/null +++ b/tests/dotnet/MySimpleApp/iOS/MySimpleApp.csproj @@ -0,0 +1,7 @@ + + + + net6.0-ios + + + diff --git a/tests/dotnet/MySimpleApp/macOS/Info.plist b/tests/dotnet/MySimpleApp/macOS/Info.plist new file mode 100644 index 000000000000..6631ffa6f242 --- /dev/null +++ b/tests/dotnet/MySimpleApp/macOS/Info.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/MySimpleApp/macOS/Makefile b/tests/dotnet/MySimpleApp/macOS/Makefile new file mode 100644 index 000000000000..5a1e3a910eec --- /dev/null +++ b/tests/dotnet/MySimpleApp/macOS/Makefile @@ -0,0 +1,9 @@ +TOP=../../../.. + +include $(TOP)/Make.config + +build: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) + +run: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) -t:Run diff --git a/tests/dotnet/MySimpleApp/macOS/MySimpleApp.csproj b/tests/dotnet/MySimpleApp/macOS/MySimpleApp.csproj new file mode 100644 index 000000000000..0af05cf82e0c --- /dev/null +++ b/tests/dotnet/MySimpleApp/macOS/MySimpleApp.csproj @@ -0,0 +1,7 @@ + + + + net6.0-macos + + + diff --git a/tests/dotnet/MySimpleApp/shared.csproj b/tests/dotnet/MySimpleApp/shared.csproj new file mode 100644 index 000000000000..ed38d1d43561 --- /dev/null +++ b/tests/dotnet/MySimpleApp/shared.csproj @@ -0,0 +1,15 @@ + + + + Exe + + MySimpleApp + com.xamarin.mysimpleapp + 3.14 + + + + + + + diff --git a/tests/dotnet/MySimpleApp/tvOS/Info.plist b/tests/dotnet/MySimpleApp/tvOS/Info.plist new file mode 100644 index 000000000000..6631ffa6f242 --- /dev/null +++ b/tests/dotnet/MySimpleApp/tvOS/Info.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/MySimpleApp/tvOS/Makefile b/tests/dotnet/MySimpleApp/tvOS/Makefile new file mode 100644 index 000000000000..5a1e3a910eec --- /dev/null +++ b/tests/dotnet/MySimpleApp/tvOS/Makefile @@ -0,0 +1,9 @@ +TOP=../../../.. + +include $(TOP)/Make.config + +build: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) + +run: + $(DOTNET6) build /bl *.csproj $(MSBUILD_VERBOSITY) -t:Run diff --git a/tests/dotnet/MySimpleApp/tvOS/MySimpleApp.csproj b/tests/dotnet/MySimpleApp/tvOS/MySimpleApp.csproj new file mode 100644 index 000000000000..940a8d55a0e7 --- /dev/null +++ b/tests/dotnet/MySimpleApp/tvOS/MySimpleApp.csproj @@ -0,0 +1,7 @@ + + + + net6.0-tvos + + + diff --git a/tests/dotnet/MySingleView/MySingleView.csproj b/tests/dotnet/MySingleView/MySingleView.csproj index 578b7ac0ec65..97b5f03d8711 100644 --- a/tests/dotnet/MySingleView/MySingleView.csproj +++ b/tests/dotnet/MySingleView/MySingleView.csproj @@ -2,7 +2,6 @@ net6.0-ios - iossimulator-x64 Exe MySingleTitle diff --git a/tests/dotnet/MyTVApp/MyTVApp.csproj b/tests/dotnet/MyTVApp/MyTVApp.csproj index 718f3966f876..29130d2334ec 100644 --- a/tests/dotnet/MyTVApp/MyTVApp.csproj +++ b/tests/dotnet/MyTVApp/MyTVApp.csproj @@ -2,7 +2,6 @@ net6.0-tvos - tvossimulator-x64 Exe diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index 480934e67622..47d0e2bae57c 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -52,6 +52,7 @@ void Clean (string project_path) } [Test] + [TestCase (null)] [TestCase ("iossimulator-x86")] [TestCase ("iossimulator-x64")] [TestCase ("ios-arm64")] @@ -63,7 +64,11 @@ public void BuildMySingleView (string runtimeIdentifier) Configuration.IgnoreIfIgnoredPlatform (platform); Clean (project_path); var properties = new Dictionary (verbosity); - properties ["RuntimeIdentifier"] = runtimeIdentifier; + if (!string.IsNullOrEmpty (runtimeIdentifier)) { + properties ["RuntimeIdentifier"] = runtimeIdentifier; + } else { + runtimeIdentifier = "iossimulator-x64"; // default RID for iOS projects. We set it here to make the rest of the test know where to expect files to be. + } var result = DotNet.AssertBuild (project_path, properties); AssertThatLinkerExecuted (result); var appPath = Path.Combine (Path.GetDirectoryName (project_path), "bin", "Debug", "net6.0-ios", runtimeIdentifier, "MySingleView.app"); @@ -77,6 +82,7 @@ public void BuildMySingleView (string runtimeIdentifier) } [Test] + [TestCase (null)] [TestCase ("osx-x64")] [TestCase ("osx-arm64")] public void BuildMyCocoaApp (string runtimeIdentifier) @@ -86,13 +92,18 @@ public void BuildMyCocoaApp (string runtimeIdentifier) Configuration.IgnoreIfIgnoredPlatform (platform); Clean (project_path); var properties = new Dictionary (verbosity); - properties ["RuntimeIdentifier"] = runtimeIdentifier; + if (!string.IsNullOrEmpty (runtimeIdentifier)) { + properties ["RuntimeIdentifier"] = runtimeIdentifier; + } else { + runtimeIdentifier = "osx-x64"; // default RID for macOS projects. We set it here to make the rest of the test know where to expect files to be. + } var result = DotNet.AssertBuild (project_path, properties); AssertThatLinkerExecuted (result); AssertAppContents (platform, Path.Combine (Path.GetDirectoryName (project_path), "bin", "Debug", "net6.0-macos", runtimeIdentifier, "MyCocoaApp.app")); } [Test] + [TestCase (null)] [TestCase ("tvossimulator-x64")] [TestCase ("tvos-arm64")] public void BuildMyTVApp (string runtimeIdentifier) @@ -102,7 +113,11 @@ public void BuildMyTVApp (string runtimeIdentifier) Configuration.IgnoreIfIgnoredPlatform (platform); Clean (project_path); var properties = new Dictionary (verbosity); - properties ["RuntimeIdentifier"] = runtimeIdentifier; + if (!string.IsNullOrEmpty (runtimeIdentifier)) { + properties ["RuntimeIdentifier"] = runtimeIdentifier; + } else { + runtimeIdentifier = "tvossimulator-x64"; // default RID for tvOS projects. We set it here to make the rest of the test know where to expect files to be. + } var result = DotNet.AssertBuild (project_path, properties); AssertThatLinkerExecuted (result); AssertAppContents (platform, Path.Combine (Path.GetDirectoryName (project_path), "bin", "Debug", "net6.0-tvos", runtimeIdentifier, "MyTVApp.app")); @@ -120,6 +135,7 @@ public void BuildMyWatchApp () } [Test] + [TestCase (null)] [TestCase ("maccatalyst-x64")] [TestCase ("maccatalyst-arm64")] public void BuildMyCatalystApp (string runtimeIdentifier) @@ -129,7 +145,11 @@ public void BuildMyCatalystApp (string runtimeIdentifier) Configuration.IgnoreIfIgnoredPlatform (platform); Clean (project_path); var properties = new Dictionary (verbosity); - properties ["RuntimeIdentifier"] = runtimeIdentifier; + if (!string.IsNullOrEmpty (runtimeIdentifier)) { + properties ["RuntimeIdentifier"] = runtimeIdentifier; + } else { + runtimeIdentifier = "maccatalyst-x64"; // default RID for Mac Catalyst projects. We set it here to make the rest of the test know where to expect files to be. + } var result = DotNet.AssertBuild (project_path, properties); AssertThatLinkerExecuted (result); var appPath = Path.Combine (Path.GetDirectoryName (project_path), "bin", "Debug", "net6.0-maccatalyst", runtimeIdentifier, "MyCatalystApp.app"); @@ -377,6 +397,89 @@ public void BuildInterdependentBindingProjects (string platform) } } + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-x86;iossimulator-x64")] + [TestCase (ApplePlatform.iOS, "ios-arm;ios-arm64")] + [TestCase (ApplePlatform.MacOSX, "osx-arm64;osx-x64")] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64;maccatalyst-x64")] + public void BuildFatApp (ApplePlatform platform, string runtimeIdentifiers) + { + var project = "MySimpleApp"; + Configuration.IgnoreIfIgnoredPlatform (platform); + + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = new Dictionary (verbosity); + properties ["RuntimeIdentifiers"] = runtimeIdentifiers; + var result = DotNet.AssertBuild (project_path, properties); + AssertThatLinkerExecuted (result); + var appPath = Path.Combine (Path.GetDirectoryName (project_path), "bin", "Debug", platform.ToFramework (), $"{project}.app"); + var infoPlistPath = GetInfoPListPath (platform, appPath); + Assert.That (infoPlistPath, Does.Exist, "Info.plist"); + var infoPlist = PDictionary.FromFile (infoPlistPath); + Assert.AreEqual ("com.xamarin.mysimpleapp", infoPlist.GetString ("CFBundleIdentifier").Value, "CFBundleIdentifier"); + Assert.AreEqual ("MySimpleApp", infoPlist.GetString ("CFBundleDisplayName").Value, "CFBundleDisplayName"); + Assert.AreEqual ("3.14", infoPlist.GetString ("CFBundleVersion").Value, "CFBundleVersion"); + Assert.AreEqual ("3.14", infoPlist.GetString ("CFBundleShortVersionString").Value, "CFBundleShortVersionString"); + } + + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-x86;iossimulator-x64")] + [TestCase (ApplePlatform.iOS, "ios-arm;ios-arm64", "MtouchLink=SdkOnly")] + [TestCase (ApplePlatform.MacOSX, "osx-arm64;osx-x64")] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64;maccatalyst-x64")] + public void BuildFatMonoTouchTest (ApplePlatform platform, string runtimeIdentifiers, params string[] additionalProperties) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + var project_path = Path.Combine (Configuration.SourceRoot, "tests", "monotouch-test", "dotnet", platform.AsString (), "monotouch-test.csproj"); + Configuration.CopyDotNetSupportingFiles (Path.GetDirectoryName (Path.GetDirectoryName (project_path))); + Configuration.CopyDotNetSupportingFiles (Path.Combine (Configuration.SourceRoot, "tests", "bindings-test", "dotnet")); + Configuration.CopyDotNetSupportingFiles (Path.Combine (Configuration.SourceRoot, "tests", "bindings-test2", "dotnet")); + Configuration.CopyDotNetSupportingFiles (Path.Combine (Configuration.SourceRoot, "tests", "EmbeddedResources", "dotnet")); + Configuration.CopyDotNetSupportingFiles (Path.Combine (Configuration.SourceRoot, "tests", "fsharplibrary", "dotnet")); + Configuration.CopyDotNetSupportingFiles (Path.Combine (Configuration.SourceRoot, "external", "Touch.Unit", "Touch.Client", "dotnet")); + Clean (project_path); + var properties = new Dictionary (verbosity); + properties ["RuntimeIdentifiers"] = runtimeIdentifiers; + if (additionalProperties != null) { + foreach (var prop in additionalProperties) { + var eq = prop.IndexOf ('='); + var name = prop.Substring (0, eq); + var value = prop.Substring (eq + 1); + properties [name] = value; + } + } + var result = DotNet.AssertBuild (project_path, properties); + var appPath = Path.Combine (Path.GetDirectoryName (project_path), "bin", "Debug", platform.ToFramework (), "monotouchtest.app"); + var infoPlistPath = GetInfoPListPath (platform, appPath); + Assert.That (infoPlistPath, Does.Exist, "Info.plist"); + var infoPlist = PDictionary.FromFile (infoPlistPath); + Assert.AreEqual ("com.xamarin.monotouch-test", infoPlist.GetString ("CFBundleIdentifier").Value, "CFBundleIdentifier"); + Assert.AreEqual ("MonoTouchTest", infoPlist.GetString ("CFBundleDisplayName").Value, "CFBundleDisplayName"); + } + + [Test] + [TestCase (ApplePlatform.iOS, "ios-arm;ios-arm64;iossimulator-x64;iossimulator-x86")] + [TestCase (ApplePlatform.iOS, "ios-arm64;iossimulator-x64")] + [TestCase (ApplePlatform.iOS, "ios-arm64;ios-arm;iossimulator-x64")] + [TestCase (ApplePlatform.iOS, "ios-arm64;iossimulator-x64;iossimulator-x86")] + [TestCase (ApplePlatform.TVOS, "tvos-arm64;tvossimulator-x64")] + public void InvalidRuntimeIdentifiers (ApplePlatform platform, string runtimeIdentifiers) + { + var project = "MySimpleApp"; + Configuration.IgnoreIfIgnoredPlatform (platform); + + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = new Dictionary (verbosity); + properties ["RuntimeIdentifiers"] = runtimeIdentifiers; + var rv = DotNet.AssertBuildFailure (project_path, properties); + var errors = BinLog.GetBuildMessages (rv.BinLogPath).Where (v => v.Type == BuildLogEventType.Error).ToArray (); + Assert.AreEqual (1, errors.Length, "Error count"); + Assert.AreEqual ($"Building for all the runtime identifiers '{runtimeIdentifiers}' at the same time isn't possible, because they represent different platform variations.", errors [0].Message, "Error message"); + } + [Test] [TestCase ("iossimulator-x64", false)] [TestCase ("ios-arm64", true)] @@ -428,6 +531,39 @@ public void BuildAndExecuteNativeReferencesTestApp (string project, ApplePlatfor } } + [Test] + [TestCase (ApplePlatform.iOS, "ios-x64")] // valid RID in a previous preview (and common mistake) + [TestCase (ApplePlatform.iOS, "iossimulator-x84")] // it's x86, not x84 + [TestCase (ApplePlatform.iOS, "iossimulator-arm64")] // we don't support this yet + [TestCase (ApplePlatform.iOS, "helloworld")] // random text + [TestCase (ApplePlatform.iOS, "osx-x64")] // valid RID for another platform + [TestCase (ApplePlatform.TVOS, "tvos-x64")] // valid RID in a previous preview (and common mistake) + [TestCase (ApplePlatform.TVOS, "tvossimulator-x46")] // it's x64, not x46 + [TestCase (ApplePlatform.TVOS, "tvossimulator-arm64")] // we don't support this yet + [TestCase (ApplePlatform.TVOS, "helloworld")] // random text + [TestCase (ApplePlatform.TVOS, "osx-x64")] // valid RID for another platform + [TestCase (ApplePlatform.MacOSX, "osx-x46")] // it's x64, not x46 + [TestCase (ApplePlatform.MacOSX, "macos-arm64")] // it's osx, not macos + [TestCase (ApplePlatform.MacOSX, "helloworld")] // random text + [TestCase (ApplePlatform.MacOSX, "ios-arm64")] // valid RID for another platform + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-x46")] // it's x64, not x46 + [TestCase (ApplePlatform.MacCatalyst, "helloworld")] // random text + [TestCase (ApplePlatform.MacCatalyst, "osx-x64")] // valid RID for another platform + public void InvalidRuntimeIdentifier (ApplePlatform platform, string runtimeIdentifier) + { + var project = "MySimpleApp"; + Configuration.IgnoreIfIgnoredPlatform (platform); + + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = new Dictionary (verbosity); + properties ["RuntimeIdentifier"] = runtimeIdentifier; + var rv = DotNet.AssertBuildFailure (project_path, properties); + var errors = BinLog.GetBuildMessages (rv.BinLogPath).Where (v => v.Type == BuildLogEventType.Error).ToArray (); + Assert.AreEqual (1, errors.Length, "Error count"); + Assert.AreEqual ($"The RuntimeIdentifier '{runtimeIdentifier}' is invalid.", errors [0].Message, "Error message"); + } + void ExecuteWithMagicWordAndAssert (string executable) { var magicWord = Guid.NewGuid ().ToString (); @@ -453,22 +589,24 @@ void AssertThatLinkerDidNotExecute (ExecutionResult result) Assert.That (output, Does.Not.Contain ("LinkerConfiguration:"), "Custom steps did not run as expected."); } - void AssertAppContents (ApplePlatform platform, string app_directory) + string GetInfoPListPath (ApplePlatform platform, string app_directory) { - string info_plist_path; switch (platform) { case ApplePlatform.iOS: case ApplePlatform.TVOS: case ApplePlatform.WatchOS: - info_plist_path = Path.Combine (app_directory, "Info.plist"); - break; + return Path.Combine (app_directory, "Info.plist"); case ApplePlatform.MacOSX: case ApplePlatform.MacCatalyst: - info_plist_path = Path.Combine (app_directory, "Contents", "Info.plist"); - break; + return Path.Combine (app_directory, "Contents", "Info.plist"); default: throw new NotImplementedException ($"Unknown platform: {platform}"); } + } + + void AssertAppContents (ApplePlatform platform, string app_directory) + { + var info_plist_path = GetInfoPListPath (platform, app_directory); Assert.That (info_plist_path, Does.Exist, "Info.plist"); var assets_path = string.Empty; diff --git a/tests/introspection/ApiFieldTest.cs b/tests/introspection/ApiFieldTest.cs index f8b987a5e7c9..e0f9e32d3c2d 100644 --- a/tests/introspection/ApiFieldTest.cs +++ b/tests/introspection/ApiFieldTest.cs @@ -50,6 +50,16 @@ protected virtual bool Skip (Type type) /// Property to be tested protected virtual bool Skip (PropertyInfo property) { + switch (property.DeclaringType.Name) { + case "AVPlayerInterstitialEventObserver": + switch (property.Name) { // deprecated + case "CurrentEventDidChangeNotification": + case "EventsDidChangeNotification": + return true; + default: + return false; + } + } return SkipDueToAttribute (property); } @@ -116,7 +126,7 @@ IEnumerable AllProperties () // looking for properties with getters only if (p.CanWrite || !p.CanRead) continue; - if (SkipDueToAttribute (p)) + if (Skip (p) || SkipDueToAttribute (p)) continue; properties.Add (p); diff --git a/tests/introspection/ApiProtocolTest.cs b/tests/introspection/ApiProtocolTest.cs index 9f1b9ebd9a39..4be6fef9ed8d 100644 --- a/tests/introspection/ApiProtocolTest.cs +++ b/tests/introspection/ApiProtocolTest.cs @@ -605,6 +605,8 @@ public void GeneralCase () // This can often by caused by [Protocol] classes with no [Model] but having a [BaseType]. // Either have both a Model and BaseType or neither switch (t.Name) { + case "AVPlayerInterstitialEventMonitor": // deprecated + continue; #if !MONOMAC case "MTLCaptureManager": case "NEHotspotConfiguration": diff --git a/tests/monotouch-test/dotnet/MacCatalyst/monotouch-test.csproj b/tests/monotouch-test/dotnet/MacCatalyst/monotouch-test.csproj index 9cadcf18bbeb..0e3ec0792a4b 100644 --- a/tests/monotouch-test/dotnet/MacCatalyst/monotouch-test.csproj +++ b/tests/monotouch-test/dotnet/MacCatalyst/monotouch-test.csproj @@ -2,7 +2,6 @@ net6.0-maccatalyst - maccatalyst-x64 Exe NET latest diff --git a/tests/monotouch-test/dotnet/iOS/monotouch-test.csproj b/tests/monotouch-test/dotnet/iOS/monotouch-test.csproj index 3ad41e6422c8..d71cb5c9553e 100644 --- a/tests/monotouch-test/dotnet/iOS/monotouch-test.csproj +++ b/tests/monotouch-test/dotnet/iOS/monotouch-test.csproj @@ -2,7 +2,6 @@ net6.0-ios - iossimulator-x64 Exe NET latest diff --git a/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj b/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj index f87ca5a31b5c..46a2c47ebb3a 100644 --- a/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj +++ b/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj @@ -2,7 +2,6 @@ net6.0-macos - osx-x64 Exe NET latest diff --git a/tests/monotouch-test/dotnet/tvOS/monotouch-test.csproj b/tests/monotouch-test/dotnet/tvOS/monotouch-test.csproj index 511a3324db42..b290d2c5fea1 100644 --- a/tests/monotouch-test/dotnet/tvOS/monotouch-test.csproj +++ b/tests/monotouch-test/dotnet/tvOS/monotouch-test.csproj @@ -2,7 +2,6 @@ net6.0-tvos - tvossimulator-x64 Exe NET;XAMCORE_3_0 latest diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/LocalizationIgnore/common-Translations.ignore b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/LocalizationIgnore/common-Translations.ignore index bc54b06ba3e2..0b111afba432 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/LocalizationIgnore/common-Translations.ignore +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/LocalizationIgnore/common-Translations.ignore @@ -18,4 +18,12 @@ E7071 InvalidFramework InvalidPlatform E7072 +E7073 +E7074 +E7075 +E7076 +E7077 +E7078 +E7079 +E7080 W0176 diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs new file mode 100644 index 000000000000..40e02d6d90e2 --- /dev/null +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -0,0 +1,279 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using Microsoft.Build.Utilities; + +using NUnit.Framework; + +using Xamarin.iOS.Tasks; +using Xamarin.Tests; +using Xamarin.Utils; + +namespace Xamarin.MacDev.Tasks { + [TestFixture] + public class MergeAppBundleTaskTest : TestBase { + [OneTimeSetUp] + public void SetUp () + { + var env = new Dictionary { + { "MSBUILD_EXE_PATH", null }, // Comes from VSMac (when running tests from inside the IDE), and it confuses 'dotnet build', so remove it. + { "MSBuildSDKsPath", null }, // Comes from MSBuild, and confuses 'dotnet build' + }; + + RunMake (Path.Combine (Configuration.RootPath, "tests", "test-libraries"), environment: env); + RunMake (Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly"), environment: env); + } + + static void RunMake (string directory, Dictionary environment = null) + { + var arguments = new List { + "-C", + directory, + "V=1", + }; + var rv = ExecutionHelper.Execute ("make", + arguments, + output: out var output, + working_directory: null, + timeout: TimeSpan.FromSeconds (30), + environment_variables: environment); + if (rv != 0) { + var failure = $"'make {StringUtils.FormatArguments (StringUtils.QuoteForProcess (arguments))}' exited with exit code {rv}:"; + var indented = "\t" + string.Join ("\n\t", output.ToString ().Split ('\n')); + Console.WriteLine (failure); + Console.WriteLine (indented); + // Only show the last 10 lines in the assert message, because otherwise the html reports can end up quite big. + var shortIndented = indented.Split ('\n').Reverse ().Take (10).Reverse (); + Assert.Fail (failure + "\n" + string.Join ("\n", shortIndented)); + } + } + + MergeAppBundles CreateTask (string outputBundle, params string[] inputBundles) + { + var inputItems = new List (); + for (var i = 0; i < inputBundles.Length; i++) { + var item = new TaskItem (inputBundles [i]); + item.SetMetadata ("SpecificSubdirectory", $"SubDir{i + 1}"); + inputItems.Add (item); + } + var task = CreateTask (); + task.InputAppBundles = inputItems.ToArray (); + task.OutputAppBundle = outputBundle; + return task; + } + + // Create two app bundles, one with fileA, and one with fileB, in the root directory + string[] CreateAppBundles (string fileA, string fileB, string fileName = null) + { + var appBundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + var appBundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + Directory.CreateDirectory (appBundleA); + Directory.CreateDirectory (appBundleB); + File.Copy (fileA, Path.Combine (appBundleA, fileName ?? Path.GetFileName (fileA))); + File.Copy (fileB, Path.Combine (appBundleB, fileName ?? Path.GetFileName (fileB))); + return new string [] { appBundleA, appBundleB }; + } + + string CreateAppBundle (string directory, params string[] files) + { + var appBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + Directory.CreateDirectory (appBundle); + foreach (var file in files) { + var inputPath = Path.Combine (directory, file); + var outputPath = Path.Combine (appBundle, file); + Directory.CreateDirectory (Path.GetDirectoryName (outputPath)); + File.Copy (inputPath, outputPath, true); + } + return appBundle; + } + + [Test] + public void TestLipoExecutable () + { + var fileA = Path.Combine (Configuration.RootPath, "tests", "test-libraries", ".libs", "macos", "libtest.arm64.dylib"); + var fileB = Path.Combine (Configuration.RootPath, "tests", "test-libraries", ".libs", "macos", "libtest.x86_64.dylib"); + var bundles = CreateAppBundles (fileA, fileB, "libtest.dylib"); + + var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); + var task = CreateTask (outputBundle, bundles); + Assert.IsTrue (task.Execute (), "Task execution"); + + // The bundle should only contain a single file. + Assert.AreEqual (1, Directory.GetFileSystemEntries (outputBundle).Length, "Files in bundle"); + + // The resulting dylib should contain 2 architectures. + var fatLibrary = Path.Combine (outputBundle, "libtest.dylib"); + Assert.That (fatLibrary, Does.Exist, "Existence"); + var machO = MachO.Read (fatLibrary).ToArray (); + Assert.AreEqual (2, machO.Length, "Architecture Count"); + } + + [Test] + public void TestPEAssembly () + { + var complexAssemblyPath = Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly", "bin", "Debug", "net6.0"); + var complexFiles = new string [] { + "ComplexAssembly.dll", + "ComplexAssembly.pdb", + "ComplexAssembly.dll.config", + "de/ComplexAssembly.resources.dll", + "en-AU/ComplexAssembly.resources.dll", + "es/ComplexAssembly.resources.dll", + }; + var appA = CreateAppBundle (complexAssemblyPath, complexFiles); + var appB = CreateAppBundle (complexAssemblyPath, complexFiles); + var bundles = new string [] { appA, appB }; + + var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); + var task = CreateTask (outputBundle, bundles); + Assert.IsTrue (task.Execute (), "Task execution"); + + // The bundle should have all the files + Assert.AreEqual (complexFiles.Length, Directory.GetFileSystemEntries (outputBundle).Length, "Files in bundle"); + + // with the same structure + foreach (var file in complexFiles) + Assert.That (Path.Combine (outputBundle, file), Does.Exist, $"File existence"); + } + + [Test] + public void TestDifferentOtherFiles () + { + var tmpDir = Cache.CreateTemporaryDirectory (); + var fileA = Path.Combine (tmpDir, "A.txt"); + var fileB = Path.Combine (tmpDir, "B.txt"); + File.WriteAllText (fileA, "A"); + File.WriteAllText (fileB, "B"); + var bundles = CreateAppBundles (fileA, fileB, "Something.txt"); + + var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); + var task = CreateTask (outputBundle, bundles); + Assert.IsFalse (task.Execute (), "Task execution"); + Assert.AreEqual (3, Engine.Logger.ErrorEvents.Count, "Errors:\n\t" + string.Join ("\n\t", Engine.Logger.ErrorEvents.Select ((v) => v.Message).ToArray ())); + Assert.AreEqual ("Unable to merge the file 'Something.txt', it's different between the input app bundles.", Engine.Logger.ErrorEvents [0].Message, "Error message"); + Assert.That (Engine.Logger.ErrorEvents [1].Message, Does.Match ("App bundle file #1: .*/MergeMe.app/Something.txt"), "Error message 2"); + Assert.That (Engine.Logger.ErrorEvents [2].Message, Does.Match ("App bundle file #2: .*/MergeMe.app/Something.txt"), "Error message 3"); + } + + [Test] + public void TestSymlinks () + { + var bundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + var bundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + var fileA = Path.Combine (bundleA, "A.txt"); + var fileB = Path.Combine (bundleB, "A.txt"); + Directory.CreateDirectory (Path.GetDirectoryName (fileA)); + File.WriteAllText (fileA, "A"); + Directory.CreateDirectory (Path.GetDirectoryName (fileB)); + File.WriteAllText (fileB, "A"); + var linkA = Path.Combine (bundleA, "B.txt"); + var linkB = Path.Combine (bundleB, "B.txt"); + Assert.IsTrue (PathUtils.Symlink ("A.txt", linkA), "Link A"); + Assert.IsTrue (PathUtils.Symlink ("A.txt", linkB), "Link B"); + + + var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); + var task = CreateTask (outputBundle, bundleA, bundleB); + Assert.IsTrue (task.Execute (), "Task execution"); + Assert.IsTrue (PathUtils.IsSymlink (Path.Combine (outputBundle, "B.txt")), "IsSymlink"); + } + + [Test] + public void TestSymlinksWithDifferentTargets () + { + var bundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + var bundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + var fileA = Path.Combine (bundleA, "A.txt"); + var fileB = Path.Combine (bundleB, "A.txt"); + var fileAC = Path.Combine (bundleA, "C.txt"); + var fileBC = Path.Combine (bundleB, "C.txt"); + Directory.CreateDirectory (Path.GetDirectoryName (fileA)); + File.WriteAllText (fileA, "A"); + File.WriteAllText (fileAC, "C"); + Directory.CreateDirectory (Path.GetDirectoryName (fileB)); + File.WriteAllText (fileB, "A"); + File.WriteAllText (fileBC, "C"); + // There's a symlink in both apps, but they have different targets. + var linkA = Path.Combine (bundleA, "B.txt"); + var linkB = Path.Combine (bundleB, "B.txt"); + Assert.IsTrue (PathUtils.Symlink ("A.txt", linkA), "Link A"); + Assert.IsTrue (PathUtils.Symlink ("C.txt", linkB), "Link B"); + + + var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); + var task = CreateTask (outputBundle, bundleA, bundleB); + Assert.IsFalse (task.Execute (), "Task execution"); + Assert.AreEqual (3, Engine.Logger.ErrorEvents.Count, "Errors:\n\t" + string.Join ("\n\t", Engine.Logger.ErrorEvents.Select ((v) => v.Message).ToArray ())); + Assert.AreEqual ("Can't merge the symlink 'B.txt', it has different targets.", Engine.Logger.ErrorEvents [0].Message, "Error message"); + Assert.That (Engine.Logger.ErrorEvents [1].Message, Does.Match ("App bundle file #1: .*/MergeMe.app/B.txt"), "Error message 2"); + Assert.That (Engine.Logger.ErrorEvents [2].Message, Does.Match ("App bundle file #2: .*/MergeMe.app/B.txt"), "Error message 3"); + } + + [Test] + public void TestDirectories () + { + var bundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + var bundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app"); + var onlyA = "A"; + var onlyB = "B"; + var bothAB = "AB"; + var nestedOnlyA = "AA/AA"; + var nestedOnlyB = "BB/BB"; + var nestedBothAB = "ABAB/ABAB"; + var nestedSharedOnlyA = "ABS/A"; + var nestedSharedOnlyB = "ABS/B"; + + Directory.CreateDirectory (Path.Combine (bundleA, onlyA)); + Directory.CreateDirectory (Path.Combine (bundleA, bothAB)); + Directory.CreateDirectory (Path.Combine (bundleA, nestedOnlyA)); + Directory.CreateDirectory (Path.Combine (bundleA, nestedBothAB)); + Directory.CreateDirectory (Path.Combine (bundleA, nestedSharedOnlyA)); + Directory.CreateDirectory (Path.Combine (bundleB, onlyB)); + Directory.CreateDirectory (Path.Combine (bundleB, nestedOnlyB)); + Directory.CreateDirectory (Path.Combine (bundleB, bothAB)); + Directory.CreateDirectory (Path.Combine (bundleB, nestedBothAB)); + Directory.CreateDirectory (Path.Combine (bundleB, nestedSharedOnlyB)); + + var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); + var task = CreateTask (outputBundle, bundleA, bundleB); + Assert.IsTrue (task.Execute (), "Task execution"); + Assert.That (Path.Combine (outputBundle, onlyA), Does.Exist, "onlyA"); + Assert.That (Path.Combine (outputBundle, onlyB), Does.Exist, "onlyB"); + Assert.That (Path.Combine (outputBundle, bothAB), Does.Exist, "bothAB"); + Assert.That (Path.Combine (outputBundle, nestedOnlyA), Does.Exist, "nestedOnlyA"); + Assert.That (Path.Combine (outputBundle, nestedOnlyB), Does.Exist, "nestedOnlyB"); + Assert.That (Path.Combine (outputBundle, nestedBothAB), Does.Exist, "nestedBothAB"); + Assert.That (Path.Combine (outputBundle, nestedSharedOnlyA), Does.Exist, "nestedSharedOnlyA"); + Assert.That (Path.Combine (outputBundle, nestedSharedOnlyB), Does.Exist, "nestedSharedOnlyB"); + + // Verify that there aren't any other directories + Assert.AreEqual (7, Directory.GetFileSystemEntries (outputBundle).Length, "Directories in bundle"); + } + + [Test] + public void TestSingleInput () + { + var fileA = Path.Combine (Configuration.RootPath, "tests", "test-libraries", ".libs", "macos", "libtest.arm64.dylib"); + var bundle = CreateAppBundle (Path.GetDirectoryName (fileA), Path.GetFileName (fileA)); + var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); + var task = CreateTask (outputBundle, bundle); + Assert.IsTrue (task.Execute (), "Task execution"); + + // The bundle should only contain a single file. + Assert.AreEqual (1, Directory.GetFileSystemEntries (outputBundle).Length, "Files in bundle"); + + // The resulting dylib should contain 1 architecture. + var nonFatBinary = Path.Combine (outputBundle, "libtest.arm64.dylib"); + Assert.That (nonFatBinary, Does.Exist, "Existence"); + var machO = MachO.Read (nonFatBinary).ToArray (); + Assert.AreEqual (1, machO.Length, "Architecture Count"); + + // and the file size should be the same as the input + Assert.That (new FileInfo (fileA).Length, Is.EqualTo (new FileInfo (nonFatBinary).Length), "File length"); + } + + } +} diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj index 91be76583d17..0627ea93623d 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/Xamarin.MacDev.Tasks.Tests.csproj @@ -5,6 +5,7 @@ false true latest + $(DefineConstants);MSBUILD_TASKS