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