From 87de6ea6538c8b70601c945189783107e23dd115 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 25 May 2021 21:43:50 +0200 Subject: [PATCH 01/42] [runtime] Add support for finding assemblies in an RID-specific subdirectory. --- runtime/product.h | 45 ++++++++++++++++++++++++++++++++++++++++++++- runtime/runtime.m | 12 +++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/runtime/product.h b/runtime/product.h index a7725d1d927f..4b57e58fd796 100644 --- a/runtime/product.h +++ b/runtime/product.h @@ -6,6 +6,8 @@ * */ +#include + #ifdef MONOTOUCH #if TARGET_OS_WATCH #define PRODUCT "Xamarin.WatchOS" @@ -43,4 +45,45 @@ #define ARCH_SUBDIR #else #error Either MONOTOUCH or MONOMAC must be defined. -#endif \ No newline at end of file +#endif + +// Set RuntimeIdentifier defines +#if TARGET_OS_MACCATALYST + #define RUNTIMEIDENTIFIER_PLATFORM "maccatalyst" +#elif TARGET_OS_IOS + #if TARGET_OS_SIMULATOR + #define RUNTIMEIDENTIFIER_PLATFORM "iossimulator" + #else + #define RUNTIMEIDENTIFIER_PLATFORM "ios" + #endif +#elif TARGET_OS_TV + #if TARGET_OS_SIMULATOR + #define RUNTIMEIDENTIFIER_PLATFORM "tvossimulator" + #else + #define RUNTIMEIDENTIFIER_PLATFORM "tvos" + #endif +#elif TARGET_OS_WATCH + #if TARGET_OS_SIMULATOR + #define RUNTIMEIDENTIFIER_PLATFORM "watchossimulator" + #else + #define RUNTIMEIDENTIFIER_PLATFORM "watchos" + #endif +#elif TARGET_OS_OSX + #define RUNTIMEIDENTIFIER_PLATFORM "osx" +#else + #error Unknown platform +#endif + +#if defined (__aarch64__) + #define RUNTIMEIDENTIFIER_ARCHITECTURE "arm64" +#elif defined (__x86_64__) + #define RUNTIMEIDENTIFIER_ARCHITECTURE "x64" +#elif defined (__i386__) + #define RUNTIMEIDENTIFIER_ARCHITECTURE "x86" +#elif defined (__arm__) + #define RUNTIMEIDENTIFIER_ARCHITECTURE "arm" +#else + #error Unknown architecture +#endif + +#define RUNTIMEIDENTIFIER RUNTIMEIDENTIFIER_PLATFORM "-" RUNTIMEIDENTIFIER_ARCHITECTURE diff --git a/runtime/runtime.m b/runtime/runtime.m index bc2e0a9d202a..bb64d9163018 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -2478,7 +2478,7 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags; * * The platform assembly (Xamarin.[iOS|TVOS|WatchOS].dll) and any assemblies * the platform assembly references (mscorlib.dll, System.dll) may be in a - * pointer-size subdirectory (ARCH_SUBDIR). + * pointer-size subdirectory (ARCH_SUBDIR), or an RID-specific subdirectory. * * AOT data files will have an arch-specific infix. */ @@ -2536,6 +2536,16 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags; } #endif // !MONOMAC +#if DOTNET + // RID-specific subdirectory + if (snprintf (path, pathlen, "%s/.xamarin/%s/%s", root, RUNTIMEIDENTIFIER, resource) < 0) { + LOG (PRODUCT ": Failed to construct path for resource: %s (5): %s", resource, strerror (errno)); + return false; + } else if (xamarin_file_exists (path)) { + return true; + } +#endif + // just the file, no extensions, etc. if (snprintf (path, pathlen, "%s/%s", root, resource) < 0) { LOG (PRODUCT ": Failed to construct path for resource: %s (6): %s", resource, strerror (errno)); From 6fe54cb7f2e051a54c940cfbb3f07ff0c55efca4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:28:55 +0200 Subject: [PATCH 02/42] [dotnet] Only trim when building a specific RuntimeIdentifier. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 20ad29ca7cb7..ea95542dc5b2 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -209,7 +209,7 @@ - true + true From 9a9562c06e0ee989d4bbc658796c7d02ddca0574 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 16 Jun 2021 00:09:17 +0200 Subject: [PATCH 03/42] [tests] Add unified simple test app for all platforms we support. This makes writing tests a bit easier, since we can use the same test project name for all platforms. --- tests/dotnet/MySimpleApp/AppDelegate.cs | 19 ++++++++++++++++++ .../dotnet/MySimpleApp/MacCatalyst/Info.plist | 6 ++++++ tests/dotnet/MySimpleApp/MacCatalyst/Makefile | 9 +++++++++ .../MacCatalyst/MySimpleApp.csproj | 7 +++++++ tests/dotnet/MySimpleApp/Makefile | 20 +++++++++++++++++++ tests/dotnet/MySimpleApp/iOS/Info.plist | 8 ++++++++ tests/dotnet/MySimpleApp/iOS/Makefile | 9 +++++++++ .../dotnet/MySimpleApp/iOS/MySimpleApp.csproj | 7 +++++++ tests/dotnet/MySimpleApp/macOS/Info.plist | 6 ++++++ tests/dotnet/MySimpleApp/macOS/Makefile | 9 +++++++++ .../MySimpleApp/macOS/MySimpleApp.csproj | 7 +++++++ tests/dotnet/MySimpleApp/shared.csproj | 15 ++++++++++++++ tests/dotnet/MySimpleApp/tvOS/Info.plist | 6 ++++++ tests/dotnet/MySimpleApp/tvOS/Makefile | 9 +++++++++ .../MySimpleApp/tvOS/MySimpleApp.csproj | 7 +++++++ 15 files changed, 144 insertions(+) create mode 100644 tests/dotnet/MySimpleApp/AppDelegate.cs create mode 100644 tests/dotnet/MySimpleApp/MacCatalyst/Info.plist create mode 100644 tests/dotnet/MySimpleApp/MacCatalyst/Makefile create mode 100644 tests/dotnet/MySimpleApp/MacCatalyst/MySimpleApp.csproj create mode 100644 tests/dotnet/MySimpleApp/Makefile create mode 100644 tests/dotnet/MySimpleApp/iOS/Info.plist create mode 100644 tests/dotnet/MySimpleApp/iOS/Makefile create mode 100644 tests/dotnet/MySimpleApp/iOS/MySimpleApp.csproj create mode 100644 tests/dotnet/MySimpleApp/macOS/Info.plist create mode 100644 tests/dotnet/MySimpleApp/macOS/Makefile create mode 100644 tests/dotnet/MySimpleApp/macOS/MySimpleApp.csproj create mode 100644 tests/dotnet/MySimpleApp/shared.csproj create mode 100644 tests/dotnet/MySimpleApp/tvOS/Info.plist create mode 100644 tests/dotnet/MySimpleApp/tvOS/Makefile create mode 100644 tests/dotnet/MySimpleApp/tvOS/MySimpleApp.csproj 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 + + + From 0021c2b4d83f6307cfd251407e8e50103a1b66fe Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 04/42] [MachO] Make StaticLibrary.IsStaticLibrary work with files shorter than 8 bytes --- tools/common/MachO.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/common/MachO.cs b/tools/common/MachO.cs index 4ac40dfa20b1..8ede1ac7a98a 100644 --- a/tools/common/MachO.cs +++ b/tools/common/MachO.cs @@ -573,7 +573,12 @@ public static bool IsStaticLibrary (BinaryReader reader, bool throw_if_error = f var pos = reader.BaseStream.Position; var bytes = reader.ReadBytes (8); - var rv = bytes [0] == '!' && bytes [1] == '<' && bytes [2] == 'a' && bytes [3] == 'r' && bytes [4] == 'c' && bytes [5] == 'h' && bytes [6] == '>' && bytes [7] == 0xa; + bool rv; + if (bytes.Length < 8) { + rv = false; + } else { + rv = bytes [0] == '!' && bytes [1] == '<' && bytes [2] == 'a' && bytes [3] == 'r' && bytes [4] == 'c' && bytes [5] == 'h' && bytes [6] == '>' && bytes [7] == 0xa; + } reader.BaseStream.Position = pos; if (throw_if_error && !rv) From 7aec86fa0498def7a3dd02f98d2ea38f52a0a03d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 05/42] =?UTF-8?q?=1B[tools]=20Create=20a=20PathUtils=20cla?= =?UTF-8?q?ss=20and=20move=20some=20shareable=20code=20there.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/common/Assembly.cs | 2 +- tools/common/Driver.cs | 58 --------------------- tools/common/PathUtils.cs | 65 ++++++++++++++++++++++++ tools/dotnet-linker/dotnet-linker.csproj | 3 ++ tools/mmp/driver.cs | 7 +-- tools/mmp/mmp.csproj | 3 ++ tools/mtouch/mtouch.cs | 8 +-- tools/mtouch/mtouch.csproj | 3 ++ 8 files changed, 81 insertions(+), 68 deletions(-) diff --git a/tools/common/Assembly.cs b/tools/common/Assembly.cs index b84e1f4b24e4..57e5f78a4053 100644 --- a/tools/common/Assembly.cs +++ b/tools/common/Assembly.cs @@ -717,7 +717,7 @@ public bool CopyAssembly (string source, string target, bool copy_debug_symbols if (!Application.IsUptodate (source, target) && (strip_assembly || !Cache.CompareAssemblies (source, target))) { copied = true; if (strip_assembly) { - Driver.FileDelete (target); + PathUtils.FileDelete (target); Directory.CreateDirectory (Path.GetDirectoryName (target)); MonoTouch.Tuner.Stripper.Process (source, target); } else { diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index 06cda1fe250c..c90f830f94d9 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -1246,63 +1246,5 @@ public static Frameworks GetFrameworks (Application app) throw ErrorHelper.CreateError (71, Errors.MX0071, app.Platform, app.ProductName); return rv; } - - [DllImport (Constants.libSystemLibrary)] - static extern int symlink (string path1, string path2); - - public static bool Symlink (string path1, string path2) - { - return symlink (path1, path2) == 0; - } - - [DllImport (Constants.libSystemLibrary)] - static extern int unlink (string pathname); - - public static void FileDelete (string file) - { - // File.Delete can't always delete symlinks (in particular if the symlink points to a file that doesn't exist). - unlink (file); - // ignore any errors. - } - - struct timespec { - public IntPtr tv_sec; - public IntPtr tv_nsec; - } - - struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */ - public uint st_dev; - public ushort st_mode; - public ushort st_nlink; - public ulong st_ino; - public uint st_uid; - public uint st_gid; - public uint st_rdev; - public timespec st_atimespec; - public timespec st_mtimespec; - public timespec st_ctimespec; - public timespec st_birthtimespec; - public ulong st_size; - public ulong st_blocks; - public uint st_blksize; - public uint st_flags; - public uint st_gen; - public uint st_lspare; - public ulong st_qspare_1; - public ulong st_qspare_2; - } - - [DllImport (Constants.libSystemLibrary, EntryPoint = "lstat$INODE64", SetLastError = true)] - static extern int lstat (string path, out stat buf); - - public static bool IsSymlink (string file) - { - stat buf; - var rv = lstat (file, out buf); - if (rv != 0) - throw new Exception (string.Format ("Could not lstat '{0}': {1}", file, Marshal.GetLastWin32Error ())); - const int S_IFLNK = 40960; - return (buf.st_mode & S_IFLNK) == S_IFLNK; - } } } diff --git a/tools/common/PathUtils.cs b/tools/common/PathUtils.cs index fadd2f9b4968..6cddc374ec59 100644 --- a/tools/common/PathUtils.cs +++ b/tools/common/PathUtils.cs @@ -114,6 +114,71 @@ public static string RelativeToAbsolute (string baseDirectory, string relative) { return Path.GetFullPath (Path.Combine (baseDirectory, relative)); } + + [DllImport ("/usr/lib/libSystem.dylib", SetLastError = true)] + static extern int symlink (string path1, string path2); + + public static bool Symlink (string target, string symlink) + { + return PathUtils.symlink (target, symlink) == 0; + } + + public static void CreateSymlink (string symlink, string target) + { + FileDelete (symlink); // Delete any existing symlinks. + var rv = PathUtils.symlink (target, symlink); + if (rv != 0) + throw new Exception (string.Format ("Could not create the symlink '{0}': {1}", symlink, Marshal.GetLastWin32Error ())); + } + [DllImport ("/usr/lib/libSystem.dylib")] + static extern int unlink (string pathname); + + // File.Delete can't always delete symlinks (in particular if the symlink points to a file that doesn't exist). + public static void FileDelete (string file) + { + unlink (file); + // ignore any errors. + } + + struct timespec { + public IntPtr tv_sec; + public IntPtr tv_nsec; + } + + struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */ + public uint st_dev; + public ushort st_mode; + public ushort st_nlink; + public ulong st_ino; + public uint st_uid; + public uint st_gid; + public uint st_rdev; + public timespec st_atimespec; + public timespec st_mtimespec; + public timespec st_ctimespec; + public timespec st_birthtimespec; + public ulong st_size; + public ulong st_blocks; + public uint st_blksize; + public uint st_flags; + public uint st_gen; + public uint st_lspare; + public ulong st_qspare_1; + public ulong st_qspare_2; + } + + [DllImport ("/usr/lib/libSystem.dylib", EntryPoint = "lstat$INODE64", SetLastError = true)] + static extern int lstat (string path, out stat buf); + + public static bool IsSymlink (string file) + { + stat buf; + var rv = lstat (file, out buf); + if (rv != 0) + throw new Exception (string.Format ("Could not lstat '{0}': {1}", file, Marshal.GetLastWin32Error ())); + const int S_IFLNK = 40960; + return (buf.st_mode & S_IFLNK) == S_IFLNK; + } } } diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index c6aa8de7d9a3..ab6ac4aa69f7 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -88,6 +88,9 @@ external\tools\common\PInvokeWrapperGenerator.cs + + external\tools\common\PathUtils.cs + external\tools\common\PListExtensions.cs diff --git a/tools/mmp/driver.cs b/tools/mmp/driver.cs index 0f1ce56615ab..b8f7e65699aa 100644 --- a/tools/mmp/driver.cs +++ b/tools/mmp/driver.cs @@ -1395,7 +1395,7 @@ static void CreateSymLink (string directory, string real, string link) { string cd = Environment.CurrentDirectory; Environment.CurrentDirectory = directory; - symlink (Path.GetFileName (real), "./" + Path.GetFileName (link)); + PathUtils.Symlink (Path.GetFileName (real), "./" + Path.GetFileName (link)); Environment.CurrentDirectory = cd; } @@ -1439,10 +1439,7 @@ static void CreateDirectoriesIfNeeded () { // Mono.Unix can't create symlinks to relative paths, it insists on the target to a full path before creating the symlink. static void CreateSymlink (string file, string target) { - unlink (file); // Delete any existing symlinks. - var rv = symlink (target, file); - if (rv != 0) - throw ErrorHelper.CreateError (1034, Errors.MM1034, file, target, Marshal.GetLastWin32Error()); + PathUtils.CreateSymlink (file, target); } static void CreateDirectoryIfNeeded (string dir) { diff --git a/tools/mmp/mmp.csproj b/tools/mmp/mmp.csproj index 6ac54e151b0f..675ef6b680a8 100644 --- a/tools/mmp/mmp.csproj +++ b/tools/mmp/mmp.csproj @@ -422,6 +422,9 @@ tools\common\FileCopier.cs + + tools\common\PathUtils.cs + tools\common\Symbols.cs diff --git a/tools/mtouch/mtouch.cs b/tools/mtouch/mtouch.cs index d8d266bebb18..2efad9616c4e 100644 --- a/tools/mtouch/mtouch.cs +++ b/tools/mtouch/mtouch.cs @@ -257,7 +257,7 @@ public static bool SymlinkAssembly (Application app, string source, string targe File.Delete (target); if (!Directory.Exists (target_dir)) Directory.CreateDirectory (target_dir); - if (!Symlink (source, target)) + if (!PathUtils.Symlink (source, target)) return false; string sconfig = source + ".config"; @@ -265,14 +265,14 @@ public static bool SymlinkAssembly (Application app, string source, string targe if (File.Exists (tconfig)) File.Delete (tconfig); if (File.Exists (sconfig)) - Symlink (sconfig, tconfig); + PathUtils.Symlink (sconfig, tconfig); string tdebug = target + ".mdb"; if (File.Exists (tdebug)) File.Delete (tdebug); string sdebug = source + ".mdb"; if (File.Exists (sdebug)) - Symlink (sdebug, tdebug); + PathUtils.Symlink (sdebug, tdebug); string tpdb = Path.ChangeExtension (target, "pdb"); if (File.Exists (tpdb)) @@ -280,7 +280,7 @@ public static bool SymlinkAssembly (Application app, string source, string targe string spdb = Path.ChangeExtension (source, "pdb"); if (File.Exists (spdb)) - Symlink (spdb, tpdb); + PathUtils.Symlink (spdb, tpdb); return true; } diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index f45e5db4298b..05bba72b1e6e 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -425,6 +425,9 @@ tools\common\BuildTasks.cs + + tools\common\PathUtils.cs + tools\common\Symbols.cs From 0af6ec5d2b95601186079c64384ed0fa75f2da2c Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 06/42] [MachO] Add a MachO.IsMachOFile method --- tools/common/MachO.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/common/MachO.cs b/tools/common/MachO.cs index 8ede1ac7a98a..c8712de69b68 100644 --- a/tools/common/MachO.cs +++ b/tools/common/MachO.cs @@ -495,6 +495,27 @@ public static bool IsDynamicFramework (string filename) return true; } + + public static bool IsMachOFile (string filename) + { + using (var fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { + if (fs.Length < 4) + return false; + using (var reader = new BinaryReader (fs)) { + var magic = reader.ReadUInt32 (); + reader.BaseStream.Position = 0; + switch (magic) { + case MH_MAGIC: + case MH_MAGIC_64: + case FAT_MAGIC: // little-endian fat binary + case FAT_CIGAM: // big-endian fat binary + return true; + default: + return false; + } + } + } + } } public class StaticLibrary From ba83560abbcbd9d7f655905ca99a89c5db1e2144 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 07/42] [tools] Move parts of the binary file comparison to a helper file to make it usable in more places --- tools/common/FileUtils.cs | 59 ++++++++++++++++++++++++ tools/common/cache.cs | 39 +--------------- tools/dotnet-linker/dotnet-linker.csproj | 3 ++ tools/mmp/mmp.csproj | 3 ++ tools/mtouch/mtouch.csproj | 3 ++ 5 files changed, 69 insertions(+), 38 deletions(-) create mode 100644 tools/common/FileUtils.cs diff --git a/tools/common/FileUtils.cs b/tools/common/FileUtils.cs new file mode 100644 index 000000000000..5a0d1d8effc2 --- /dev/null +++ b/tools/common/FileUtils.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; + +namespace Xamarin.Utils { + internal static class FileUtils { + + public static bool CompareFiles (string a, string b) + { + using (var astream = new FileStream (a, FileMode.Open, FileAccess.Read, FileShare.Read)) { + using (var bstream = new FileStream (b, FileMode.Open, FileAccess.Read, FileShare.Read)) { + bool rv; + rv = CompareStreams (astream, bstream); + return rv; + } + } + } + + public unsafe static bool CompareStreams (Stream astream, Stream bstream) + { + if (astream.Length != bstream.Length) + return false; + + var ab = new byte [2048]; + var bb = new byte [2048]; + + do { + int ar = astream.Read (ab, 0, ab.Length); + int br = bstream.Read (bb, 0, bb.Length); + + if (ar != br) + return false; + + if (ar == 0) + return true; + + fixed (byte* aptr = ab, bptr = bb) { + long* l1 = (long*) aptr; + long* l2 = (long*) bptr; + int len = ar; + // Compare one long at a time. + for (int i = 0; i < len / 8; i++) { + if (l1 [i] != l2 [i]) + return false; + } + // Compare any remaining bytes. + int mod = len % 8; + if (mod > 0) { + for (int i = len - mod; i < len; i++) { + if (ab [i] != bb [i]) + return false; + } + } + } + } while (true); + } + } +} diff --git a/tools/common/cache.cs b/tools/common/cache.cs index 505aa098f34a..3a25a7ac907f 100644 --- a/tools/common/cache.cs +++ b/tools/common/cache.cs @@ -146,44 +146,7 @@ public unsafe static bool CompareStreams (Stream astream, Stream bstream, bool i return false; } - var ab = new byte[2048]; - var bb = new byte[2048]; - - do { - int ar = astream.Read (ab, 0, ab.Length); - int br = bstream.Read (bb, 0, bb.Length); - - if (ar != br) { - Driver.Log (6, " > streams are considered different because their lengths do not match."); - return false; - } - - if (ar == 0) - return true; - - fixed (byte *aptr = ab, bptr = bb) { - long *l1 = (long *) aptr; - long *l2 = (long *) bptr; - int len = ar; - // Compare one long at a time. - for (int i = 0; i < len / 8; i++) { - if (l1 [i] != l2 [i]) { - Driver.Log (6, " > streams differ at index {0}-{1}", i, i + 8); - return false; - } - } - // Compare any remaining bytes. - int mod = len % 8; - if (mod > 0) { - for (int i = len - mod; i < len; i++) { - if (ab [i] != bb [i]) { - Driver.Log (6, " > streams differ at byte index {0}", i); - return false; - } - } - } - } - } while (true); + return FileUtils.CompareStreams (astream, bstream); } string GetArgumentsForCacheData (Application app) diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index ab6ac4aa69f7..f3b59d5c99cf 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -67,6 +67,9 @@ external\tools\common\FileCopier.cs + + external\tools\common\FileUtils.cs + external\tools\common\LinkMode.cs diff --git a/tools/mmp/mmp.csproj b/tools/mmp/mmp.csproj index 675ef6b680a8..874a29123099 100644 --- a/tools/mmp/mmp.csproj +++ b/tools/mmp/mmp.csproj @@ -422,6 +422,9 @@ tools\common\FileCopier.cs + + tools\common\FileUtils.cs + tools\common\PathUtils.cs diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index 05bba72b1e6e..1b3a536e9c7b 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -425,6 +425,9 @@ tools\common\BuildTasks.cs + + tools\common\FileUtils.cs + tools\common\PathUtils.cs From f864ff3de705fe0cb80385412dfd93682cc97f92 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 16 Jun 2021 18:24:32 +0200 Subject: [PATCH 08/42] [tools] Move another IsUptodate overload to the FileCopier file to increase code sharing --- tools/common/Application.cs | 50 +------------------------------ tools/common/FileCopier.cs | 59 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/tools/common/Application.cs b/tools/common/Application.cs index f402f4fedfaf..8bf93ee708a6 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -645,55 +645,7 @@ public static bool UpdateFile (string source, string target, bool check_contents // if it's later than the timestamp of the "target" file itself. public static bool IsUptodate (IEnumerable sources, IEnumerable targets, bool check_stamp = true) { - if (Driver.Force) - return false; - - DateTime max_source = DateTime.MinValue; - string max_s = null; - - if (sources.Count () == 0 || targets.Count () == 0) - throw ErrorHelper.CreateError (1013, Errors.MT1013); - - foreach (var s in sources) { - var sfi = new FileInfo (s); - if (!sfi.Exists) { - Driver.Log (3, "Prerequisite '{0}' does not exist.", s); - return false; - } - - var st = sfi.LastWriteTimeUtc; - if (st > max_source) { - max_source = st; - max_s = s; - } - } - - - foreach (var t in targets) { - var tfi = new FileInfo (t); - if (!tfi.Exists) { - Driver.Log (3, "Target '{0}' does not exist.", t); - return false; - } - - if (check_stamp) { - var tfi_stamp = new FileInfo (t + ".stamp"); - if (tfi_stamp.Exists && tfi_stamp.LastWriteTimeUtc > tfi.LastWriteTimeUtc) { - Driver.Log (3, "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp", t, tfi_stamp.LastWriteTimeUtc, tfi.LastWriteTimeUtc); - tfi = tfi_stamp; - } - } - - var lwt = tfi.LastWriteTimeUtc; - if (max_source > lwt) { - Driver.Log (3, "Prerequisite '{0}' is newer than target '{1}' ({2} vs {3}).", max_s, t, max_source, lwt); - return false; - } - } - - Driver.Log (3, "Prerequisite(s) '{0}' are all older than the target(s) '{1}'.", string.Join ("', '", sources.ToArray ()), string.Join ("', '", targets.ToArray ())); - - return true; + return FileCopier.IsUptodate (sources, targets, check_stamp); } public static void UpdateDirectory (string source, string target) diff --git a/tools/common/FileCopier.cs b/tools/common/FileCopier.cs index cbba24f677af..6b3623ad3df5 100644 --- a/tools/common/FileCopier.cs +++ b/tools/common/FileCopier.cs @@ -198,6 +198,65 @@ public static bool IsUptodate (string source, string target, bool check_contents return false; } + // Checks if any of the source files have a time stamp later than any of the target files. + // + // If check_stamp is true, the function will use the timestamp of a "target".stamp file + // if it's later than the timestamp of the "target" file itself. + public static bool IsUptodate (IEnumerable sources, IEnumerable targets, bool check_stamp = true) + { +#if MMP || MTOUCH // msbuild does not have force + if (Driver.Force) + return false; +#endif + + DateTime max_source = DateTime.MinValue; + string max_s = null; + + if (sources.Count () == 0 || targets.Count () == 0) + throw ErrorHelper.CreateError (1013, Errors.MT1013); + + foreach (var s in sources) { + var sfi = new FileInfo (s); + if (!sfi.Exists) { + Log (3, "Prerequisite '{0}' does not exist.", s); + return false; + } + + var st = sfi.LastWriteTimeUtc; + if (st > max_source) { + max_source = st; + max_s = s; + } + } + + + foreach (var t in targets) { + var tfi = new FileInfo (t); + if (!tfi.Exists) { + Log (3, "Target '{0}' does not exist.", t); + return false; + } + + if (check_stamp) { + var tfi_stamp = new FileInfo (t + ".stamp"); + if (tfi_stamp.Exists && tfi_stamp.LastWriteTimeUtc > tfi.LastWriteTimeUtc) { + Log (3, "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp", t, tfi_stamp.LastWriteTimeUtc, tfi.LastWriteTimeUtc); + tfi = tfi_stamp; + } + } + + var lwt = tfi.LastWriteTimeUtc; + if (max_source > lwt) { + Log (3, "Prerequisite '{0}' is newer than target '{1}' ({2} vs {3}).", max_s, t, max_source, lwt); + return false; + } + } + + Log (3, "Prerequisite(s) '{0}' are all older than the target(s) '{1}'.", string.Join ("', '", sources.ToArray ()), string.Join ("', '", targets.ToArray ())); + + return true; + } + [DllImport ("/usr/lib/libSystem.dylib", SetLastError = true, EntryPoint = "strerror")] static extern IntPtr _strerror (int errno); From 5955cd1c92b46681bd2d216d74f23a5f5e367d87 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 09/42] [tests] Add a complex test library project with resources and app configs. --- .../TestProjects/ComplexAssembly/App.config | 4 ++ .../ComplexAssembly/ComplexAssembly.cs | 13 ++++ .../ComplexAssembly/ComplexAssembly.csproj | 17 +++++ .../TestProjects/ComplexAssembly/Makefile | 12 ++++ .../TestProjects/ComplexAssembly/README.md | 10 +++ .../ComplexAssembly/Welcome.de.resx | 65 +++++++++++++++++++ .../ComplexAssembly/Welcome.en-AU.resx | 65 +++++++++++++++++++ .../ComplexAssembly/Welcome.es.resx | 65 +++++++++++++++++++ .../TestProjects/ComplexAssembly/Welcome.resx | 65 +++++++++++++++++++ 9 files changed, 316 insertions(+) create mode 100644 tests/common/TestProjects/ComplexAssembly/App.config create mode 100644 tests/common/TestProjects/ComplexAssembly/ComplexAssembly.cs create mode 100644 tests/common/TestProjects/ComplexAssembly/ComplexAssembly.csproj create mode 100644 tests/common/TestProjects/ComplexAssembly/Makefile create mode 100644 tests/common/TestProjects/ComplexAssembly/README.md create mode 100644 tests/common/TestProjects/ComplexAssembly/Welcome.de.resx create mode 100644 tests/common/TestProjects/ComplexAssembly/Welcome.en-AU.resx create mode 100644 tests/common/TestProjects/ComplexAssembly/Welcome.es.resx create mode 100644 tests/common/TestProjects/ComplexAssembly/Welcome.resx diff --git a/tests/common/TestProjects/ComplexAssembly/App.config b/tests/common/TestProjects/ComplexAssembly/App.config new file mode 100644 index 000000000000..9f4502233993 --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/App.config @@ -0,0 +1,4 @@ + + + + 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..e33d1331496a --- /dev/null +++ b/tests/common/TestProjects/ComplexAssembly/Makefile @@ -0,0 +1,12 @@ +TOP=../../../.. + +include $(TOP)/Make.config + +prepare: + $(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) prepare + $(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 100644 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 100644 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 100644 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 100644 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 + + + From 724d0fe1b318568c76e69a2c001e2307881d8e7c Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 14 Jun 2021 20:35:24 +0200 Subject: [PATCH 10/42] [dotnet] Set a default RuntimeIdentifier and validate that we get a valid RuntimeIdentifier. Fixes #10861. Also add tests: * Remove RuntimeIdentifier from a few sample projects. These projects should continue to build just fine. * Add tests for invalid RuntimeIdentifiers. Fixes https://github.com/xamarin/xamarin-macios/issues/10861. --- .../Xamarin.Shared.Sdk.DefaultItems.targets | 25 ++++++++ dotnet/targets/Xamarin.Shared.Sdk.targets | 12 ++++ tests/dotnet/MyCocoaApp/MyCocoaApp.csproj | 1 - tests/dotnet/MySingleView/MySingleView.csproj | 1 - tests/dotnet/MyTVApp/MyTVApp.csproj | 1 - tests/dotnet/UnitTests/ProjectTest.cs | 61 +++++++++++++++++-- .../dotnet/MacCatalyst/monotouch-test.csproj | 1 - .../dotnet/iOS/monotouch-test.csproj | 1 - .../dotnet/macOS/monotouch-test.csproj | 1 - .../dotnet/tvOS/monotouch-test.csproj | 1 - 10 files changed, 94 insertions(+), 11 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets index 92a721e661b8..b6015056c8b9 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets @@ -29,6 +29,31 @@ $(MtouchArch) + + + 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 ea95542dc5b2..af20ba39a711 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -790,6 +790,18 @@ + + + <_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/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/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..4d80708077cc 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"); @@ -428,6 +448,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 (); diff --git a/tests/monotouch-test/dotnet/MacCatalyst/monotouch-test.csproj b/tests/monotouch-test/dotnet/MacCatalyst/monotouch-test.csproj index 52993feb16fa..65350fcfe7fb 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 3561dce879d4..9960c4d67d32 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 2ec163a69cb9..e65bb1109de5 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 1d483e9d3be9..cfa43152ba45 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 From e1f31c60b627dfe03a0d550e2129935087df36e2 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 11/42] [tests] Quote parameters containing semicolons when passed to 'dotnet build' --- tests/common/DotNet.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/common/DotNet.cs b/tests/common/DotNet.cs index 6d984cc3812c..1aa13135c444 100644 --- a/tests/common/DotNet.cs +++ b/tests/common/DotNet.cs @@ -49,8 +49,19 @@ public static ExecutionResult Execute (string verb, string project, Dictionary= 0) { + // https://github.com/dotnet/msbuild/issues/471 + // Escaping the semi colon like the issue suggests at one point doesn't work, because in + // that case MSBuild won't split the string into its parts for tasks that take a string[]. + // This means that a task that takes a "string[] RuntimeIdentifiers" will get an array with + // a single element, where that single element is the whole RuntimeIdentifiers string. + // Example task: https://github.com/dotnet/sdk/blob/ffca47e9a36652da2e7041360f2201a2ba197194/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs#L45 + args.Add ($"/p:{prop.Key}=\"{prop.Value}\""); + } else { + args.Add ($"/p:{prop.Key}={prop.Value}"); + } + } } var binlogPath = Path.Combine (Path.GetDirectoryName (project), $"log-{verb}-{DateTime.Now:yyyyMMdd_HHmmss}.binlog"); args.Add ($"/bl:{binlogPath}"); From 003cf30812d9b8ecdbb8d7f67a9ba71b918e47e9 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 12/42] [dotnet] Don't execute ComputeResolvedFileToPublishList if we're in the outer build of a multi-rid build. There's nothing to publish (except the merged app bundle, which we'll handle ourselves). --- dotnet/targets/Xamarin.Shared.Sdk.targets | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index af20ba39a711..445c707150f1 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -496,7 +496,18 @@ - + + <_ComputeVariablesDependsOn>_GenerateBundleName;_ComputeFrameworkVariables + + <_ComputeVariablesDependsOn Condition="'$(RuntimeIdentifiers)' == ''">$(_ComputeVariablesDependsOn);ComputeResolvedFilesToPublishList + + + <_IntermediateNativeLibraryDir>$(IntermediateOutputPath)nativelibraries/ <_NativeExecutableName>$(_AppBundleName) From 5c50a2faa3dae3706c6adbfee596be13000f21f9 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 13/42] [msbuild] Implement a MergeAppBundles task to merge two (or more) apps into a single universal/fat app. --- .../MSBStrings.Designer.cs | 63 +++ .../MSBStrings.resx | 28 ++ .../ErrorHelper.msbuild.cs | 53 +++ .../Tasks/MergeAppBundlesTaskBase.cs | 438 ++++++++++++++++++ .../Xamarin.MacDev.Tasks.Core.csproj | 16 + .../Tasks/MergeAppBundles.cs | 20 + src/ObjCRuntime/ErrorHelper.cs | 2 +- tools/common/MachO.cs | 9 + tools/common/PathUtils.cs | 20 + 9 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 msbuild/Xamarin.MacDev.Tasks.Core/ErrorHelper.msbuild.cs create mode 100644 msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs create mode 100644 msbuild/Xamarin.MacDev.Tasks/Tasks/MergeAppBundles.cs diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs index b0a8a2fdb016..f1841e95e6f9 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs @@ -1813,6 +1813,69 @@ 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 Invalid framework: {0}. /// diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index f6ffc6b5543b..dcae77da3e98 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1259,4 +1259,32 @@ 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. + 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..fad1b3907f6c --- /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 "MSB"; + } + } + + 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/Tasks/MergeAppBundlesTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs new file mode 100644 index 000000000000..63446e588728 --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs @@ -0,0 +1,438 @@ +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; } + + // 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 (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); + 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); + } + } + + // 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); + 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); + break; + default: + Log.LogError (MSBStrings.E7077 /* Unable to merge the file '{0}', it's different between the input app bundles. */, entries [0].RelativePath); + break; + } + } + + return !Log.HasLoggedErrors; + } + + 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/src/ObjCRuntime/ErrorHelper.cs b/src/ObjCRuntime/ErrorHelper.cs index a26a775d574b..bd35cbdf7961 100644 --- a/src/ObjCRuntime/ErrorHelper.cs +++ b/src/ObjCRuntime/ErrorHelper.cs @@ -11,7 +11,7 @@ using ProductException=ObjCRuntime.RuntimeException; #endif -#if BUNDLER +#if BUNDLER || MSBUILD_TASKS namespace Xamarin.Bundler { #else namespace ObjCRuntime { diff --git a/tools/common/MachO.cs b/tools/common/MachO.cs index c8712de69b68..6bff2996e947 100644 --- a/tools/common/MachO.cs +++ b/tools/common/MachO.cs @@ -607,6 +607,15 @@ public static bool IsStaticLibrary (BinaryReader reader, bool throw_if_error = f return rv; } + + public static bool IsStaticLibrary (string filename, bool throw_if_error = false) + { + using (var fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { + using (var reader = new BinaryReader (fs)) { + return IsStaticLibrary (reader, throw_if_error); + } + } + } } public class MachOFile diff --git a/tools/common/PathUtils.cs b/tools/common/PathUtils.cs index 6cddc374ec59..3c76f60585af 100644 --- a/tools/common/PathUtils.cs +++ b/tools/common/PathUtils.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Text; namespace Xamarin.Utils { @@ -130,6 +131,25 @@ public static void CreateSymlink (string symlink, string target) if (rv != 0) throw new Exception (string.Format ("Could not create the symlink '{0}': {1}", symlink, Marshal.GetLastWin32Error ())); } + + [DllImport ("/usr/lib/libSystem.dylib", SetLastError = true)] + static extern int readlink (string path, [Out] byte[] buffer, IntPtr len); + + public static string GetSymlinkTarget (string path) + { + byte [] buffer = null; + int rv; + do { + buffer = new byte [(buffer?.Length ?? 0) + 1024]; + rv = readlink (path, buffer, (IntPtr) (buffer.Length - 1)); + } while (rv == buffer.Length - 1); + + if (rv == -1) + throw new Exception (string.Format ("Could not readlink '{0}': {1}", path, Marshal.GetLastWin32Error ())); + + return Encoding.UTF8.GetString (buffer, 0, rv); + } + [DllImport ("/usr/lib/libSystem.dylib")] static extern int unlink (string pathname); From 4f8e9a872e7228d0b0741a1ff8d849f6101cd077 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 21 May 2021 22:29:08 +0200 Subject: [PATCH 14/42] [tests] Add new tests for merging app bundles --- tests/common/ErrorHelper.tests.cs | 58 ++++ tests/common/ExecutionHelper.cs | 6 + tests/dotnet/UnitTests/ProjectTest.cs | 96 ++++++- .../TaskTests/MergeAppBundleTaskTest.cs | 261 ++++++++++++++++++ .../Xamarin.MacDev.Tasks.Tests.csproj | 10 + 5 files changed, 425 insertions(+), 6 deletions(-) create mode 100644 tests/common/ErrorHelper.tests.cs create mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs diff --git a/tests/common/ErrorHelper.tests.cs b/tests/common/ErrorHelper.tests.cs new file mode 100644 index 000000000000..56ffb5b8c0b1 --- /dev/null +++ b/tests/common/ErrorHelper.tests.cs @@ -0,0 +1,58 @@ +// Copyright 2020, Microsoft Corp. All rights reserved, + +using System; +using System.Collections.Generic; +using System.Linq; + +using Mono.Cecil; +using Mono.Cecil.Cil; + +using Xamarin.Utils; + +namespace Xamarin.Bundler { + public static partial class ErrorHelper { + public static ApplePlatform Platform; + + internal static string Prefix { + get { + return "TESTS"; + } + } + + 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/tests/common/ExecutionHelper.cs b/tests/common/ExecutionHelper.cs index a38f28542243..b9b2fcbe017a 100644 --- a/tests/common/ExecutionHelper.cs +++ b/tests/common/ExecutionHelper.cs @@ -73,6 +73,12 @@ public static int Execute (string fileName, IList arguments, out StringB return Execute (fileName, arguments, out var _, workingDirectory: working_directory, stdout: output, stderr: output, timeout: timeout); } + public static int Execute (string fileName, IList arguments, out StringBuilder output, string working_directory, Dictionary environment_variables, TimeSpan? timeout = null) + { + output = new StringBuilder (); + return Execute (fileName, arguments, out var _, workingDirectory: working_directory, stdout: output, stderr: output, timeout: timeout, environment_variables: environment_variables); + } + public static int Execute (string fileName, IList arguments, out bool timed_out, string workingDirectory = null, Dictionary environment_variables = null, StringBuilder stdout = null, StringBuilder stderr = null, TimeSpan? timeout = null) { var rv = Execution.RunWithStringBuildersAsync (fileName, arguments, workingDirectory: workingDirectory, environment: environment_variables, standardOutput: stdout, standardError: stderr, timeout: timeout).Result; diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index 4d80708077cc..e569bbc72d5c 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -397,6 +397,88 @@ 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", "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)] @@ -506,22 +588,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/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs new file mode 100644 index 000000000000..96db66170142 --- /dev/null +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -0,0 +1,261 @@ +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. + }; + + Assert.AreEqual (0, ExecutionHelper.Execute ("make", + new string [] { "-C", Path.Combine (Configuration.RootPath, "tests", "test-libraries"), "-j8" }, + output: out var _, + working_directory: null, + timeout: TimeSpan.FromSeconds (30), + environment_variables: env)); + + Assert.AreEqual (0, ExecutionHelper.Execute ("make", + new string [] { "-C", Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly"), "-j8", "V=1" }, + output: out var output, + working_directory: null, + timeout: TimeSpan.FromSeconds (30), + environment_variables: env)); + } + + 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 (1, Engine.Logger.ErrorEvents.Count, "Errors"); + Assert.AreEqual ("Unknown merge file type: Other", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error + } + + [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 (1, Engine.Logger.ErrorEvents.Count, "Errors"); + Assert.AreEqual ("Can't merge symlinks with different targets.", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error + } + + [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 - + + _ComputePublishTrimmed; BuildOnlySettings; _CollectBundleResources; @@ -167,9 +169,28 @@ Codesign; + + + _ComputePublishTrimmed; + BuildOnlySettings; + _CollectBundleResources; + _PackLibraryResources; + _UnpackLibraryResources; + $(BuildDependsOn); + _CreateAppBundle; + + + + + _RunRidSpecificBuild; + _CreateMergedAppBundle; + Codesign; + + - + + _DetectAppManifest; _CopyResourcesToBundle; _CompileCoreMLModels; @@ -193,11 +214,17 @@ _CopyAppExtensionsToBundle; - + + _CreateDebugSettings; _CreateDebugConfiguration; $(CreateAppBundleDependsOn); + + + + _CreateMergedAppBundle; + @@ -213,6 +240,79 @@ + + + + <_AssemblyPublishDirectory Include="$(MSBuildProjectDirectory)/$(_AppBundlePath)" RuntimeIdentifier="$(RuntimeIdentifier)" /> + + + + + + + <_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) + + + + + + + + <_ArchitectureSpecificFiles Include="$(_RuntimeConfigurationFile)" Condition="'$(GenerateRuntimeConfigurationFiles)' != ''" /> + + + + + + + + + diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.de.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.de.resx old mode 100644 new mode 100755 diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.en-AU.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.en-AU.resx old mode 100644 new mode 100755 diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.es.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.es.resx old mode 100644 new mode 100755 diff --git a/tests/common/TestProjects/ComplexAssembly/Welcome.resx b/tests/common/TestProjects/ComplexAssembly/Welcome.resx old mode 100644 new mode 100755 From 81b139eab07b08c88534c5124cd20c2a320a3060 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 16 Jun 2021 16:23:37 +0200 Subject: [PATCH 16/42] [dotnet] Ignore RuntimeIdentifier if RuntimeIdentifiers is set. It's not clear what we're supposed to do if both RuntimeIdentifier and RuntimeIdentifiers are set, so just handle this case as if only RuntimeIdentifiers is set, by clearing out the RuntimeIdentifier value. Also show a warning for this scenario. --- dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets | 6 ++++++ dotnet/targets/Xamarin.Shared.Sdk.targets | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets index b6015056c8b9..425dbcff1556 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets @@ -53,6 +53,12 @@ $(IntermediateOutputPath)$(RuntimeIdentifier)\ $(OutputPath)$(RuntimeIdentifier)\ + + + + <_RuntimeIdentifiersClashMessage>Both RuntimeIdentifier and RuntimeIdentifiers are set. The value for RuntimeIdentifier will be ignored. + + diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 2aae3856e785..180791f7b760 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -240,6 +240,10 @@ + + + + Date: Fri, 18 Jun 2021 13:47:33 +0200 Subject: [PATCH 17/42] [msbuild] Fix symlink check to check for file presence first. --- .../Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs index 63446e588728..f2064c14b410 100644 --- a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs @@ -202,7 +202,7 @@ public void CopyTo (string outputDirectory, string subDirectory = null) } else if (Type == FileType.Symlink) { Directory.CreateDirectory (Path.GetDirectoryName (outputFile)); var symlinkTarget = PathUtils.GetSymlinkTarget (FullPath); - if (PathUtils.IsSymlink (outputFile) && PathUtils.GetSymlinkTarget (outputFile) == symlinkTarget) { + 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); From 4526a5ee449bb1ff741a8fa476410c414ee38b6f Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 18 Jun 2021 13:47:50 +0200 Subject: [PATCH 18/42] [msbuild] Copy directories correctly. --- .../Tasks/MergeAppBundlesTaskBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs index f2064c14b410..8dcaa9e81a29 100644 --- a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs @@ -232,6 +232,11 @@ public override bool Execute () 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; From bc9e00734399ee59a33e8db99f9c25d125a13948 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 18 Jun 2021 13:49:17 +0200 Subject: [PATCH 19/42] [tests] Updated list of not-yet-localized error messages. --- .../LocalizationIgnore/common-Translations.ignore | 7 +++++++ 1 file changed, 7 insertions(+) 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..8022e3ee3a71 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,11 @@ E7071 InvalidFramework InvalidPlatform E7072 +E7073 +E7074 +E7075 +E7076 +E7077 +E7078 +E7079 W0176 From 718d9548a3a0aa85aee7bc9f33f6754b851feed3 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 21 Jun 2021 11:45:21 +0200 Subject: [PATCH 20/42] [tests] Improve error reporting when SetUp fails in MergeAppBundleTaskTest. --- .../TaskTests/MergeAppBundleTaskTest.cs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index 96db66170142..b62cd0572b1a 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -22,19 +22,33 @@ public void SetUp () { "MSBUILD_EXE_PATH", null }, // Comes from VSMac (when running tests from inside the IDE), and it confuses 'dotnet build', so remove it. }; - Assert.AreEqual (0, ExecutionHelper.Execute ("make", - new string [] { "-C", Path.Combine (Configuration.RootPath, "tests", "test-libraries"), "-j8" }, - output: out var _, - working_directory: null, - timeout: TimeSpan.FromSeconds (30), - environment_variables: env)); + RunMake (Path.Combine (Configuration.RootPath, "tests", "test-libraries"), environment: env); + RunMake (Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly"), environment: env); + } - Assert.AreEqual (0, ExecutionHelper.Execute ("make", - new string [] { "-C", Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly"), "-j8", "V=1" }, + static void RunMake (string directory, Dictionary environment = null, int j = 8) + { + var arguments = new List { + "-C", + directory, + $"-j{j}", + "V=1", + }; + var rv = ExecutionHelper.Execute ("make", + arguments, output: out var output, working_directory: null, timeout: TimeSpan.FromSeconds (30), - environment_variables: env)); + 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) From f1cc00ce3a5b9953cb9a5e87039c643e558e40e6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 21 Jun 2021 11:45:50 +0200 Subject: [PATCH 21/42] [tests] Update tests according to the actual error messages. --- .../TaskTests/MergeAppBundleTaskTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index b62cd0572b1a..fba73e563200 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -153,7 +153,7 @@ public void TestDifferentOtherFiles () var task = CreateTask (outputBundle, bundles); Assert.IsFalse (task.Execute (), "Task execution"); Assert.AreEqual (1, Engine.Logger.ErrorEvents.Count, "Errors"); - Assert.AreEqual ("Unknown merge file type: Other", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error + Assert.AreEqual ("Unable to merge the file 'Something.txt', it's different between the input app bundles.", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error } [Test] @@ -205,7 +205,7 @@ public void TestSymlinksWithDifferentTargets () var task = CreateTask (outputBundle, bundleA, bundleB); Assert.IsFalse (task.Execute (), "Task execution"); Assert.AreEqual (1, Engine.Logger.ErrorEvents.Count, "Errors"); - Assert.AreEqual ("Can't merge symlinks with different targets.", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error + Assert.AreEqual ("Can't merge the symlink 'B.txt', it has different targets.", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error } [Test] From 7179cac047a7c7fac5597432ec666be8629efc9a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 21 Jun 2021 12:51:20 +0200 Subject: [PATCH 22/42] [dotnet] Set the default RuntimeIdentifier earlier. --- .../Xamarin.Shared.Sdk.DefaultItems.targets | 24 ------------------ dotnet/targets/Xamarin.Shared.Sdk.props | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets index 425dbcff1556..218cc3896e1e 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets @@ -29,30 +29,6 @@ $(MtouchArch) - - - iossimulator-x64 - tvossimulator-x64 - osx-x64 - maccatalyst-x64 - - true - <_RuntimeIdentifierUsesAppHost>false - false - $(IntermediateOutputPath)$(RuntimeIdentifier)\ - $(OutputPath)$(RuntimeIdentifier)\ - 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)\ + From 2f1667e593224e1fb06074c84488fc2d13a9d3c6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 22 Jun 2021 16:52:27 +0200 Subject: [PATCH 23/42] [tests] Remove MSBuildSDKsPath from the environment before invoking make which may call 'dotnet build'. --- .../TaskTests/MergeAppBundleTaskTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index fba73e563200..fa90d1950bc5 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -20,6 +20,7 @@ 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); From 889cf99b743f8515b2dead39a7d712f69b6df931 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 22 Jun 2021 17:28:18 +0200 Subject: [PATCH 24/42] [dotnet] Compile entitlements before signing the merged app bundle. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 180791f7b760..084d499e8208 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -183,6 +183,7 @@ _RunRidSpecificBuild; + _CompileEntitlements; _CreateMergedAppBundle; Codesign; @@ -223,6 +224,7 @@ + _CompileEntitlements; _CreateMergedAppBundle; From 5ecb7ae3374acae2228178d47a9456f9a034b40e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 23 Jun 2021 09:31:02 +0200 Subject: [PATCH 25/42] [msbuild] List all files causing errors. --- .../MSBStrings.Designer.cs | 9 +++++++++ msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx | 9 +++++++++ .../Tasks/MergeAppBundlesTaskBase.cs | 10 ++++++++++ .../LocalizationIgnore/common-Translations.ignore | 1 + 4 files changed, 29 insertions(+) diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs index f1841e95e6f9..056ba8736032 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs @@ -1876,6 +1876,15 @@ public static string E7079 { } } + /// + /// 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 dcae77da3e98..71a23a1d40f3 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1287,4 +1287,13 @@ 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/Tasks/MergeAppBundlesTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs index 8dcaa9e81a29..a94ab93a0acf 100644 --- a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs @@ -330,6 +330,7 @@ bool MergeAppBundles () 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; } } @@ -374,9 +375,11 @@ bool MergeAppBundles () 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; } } @@ -384,6 +387,13 @@ bool MergeAppBundles () 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) { 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 8022e3ee3a71..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 @@ -25,4 +25,5 @@ E7076 E7077 E7078 E7079 +E7080 W0176 From 2a203b53bb8529c795f24fd6b5a4ce980b0b9bfc Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 23 Jun 2021 09:31:53 +0200 Subject: [PATCH 26/42] [msbuild] Compute input files slightly differently for CreateMergedAppBundle. So that MSBuild doesn't execute CreateMergedAppBundle twice, each time with a different input. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 084d499e8208..1a1e602e7f29 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -286,11 +286,12 @@ <_AssemblyPublishDirectories> .xamarin/%(RuntimeIdentifier) + <_AssemblyPublishInputs Include="@(_AssemblyPublishDirectories -> '%(Identity)/**')" /> From f6e9f555de280bc8b8bb163792b5ee0bbf5ed4a9 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 23 Jun 2021 09:44:40 +0200 Subject: [PATCH 27/42] [runtime] Add a xamarin_locate_app_resource function. --- runtime/monovm-bridge.m | 5 ++--- runtime/runtime.m | 7 +++++++ runtime/xamarin/runtime.h | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/runtime/monovm-bridge.m b/runtime/monovm-bridge.m index 68f4ea72bad8..40e8adaf4b68 100644 --- a/runtime/monovm-bridge.m +++ b/runtime/monovm-bridge.m @@ -409,10 +409,9 @@ return; } - const char *app_path = xamarin_get_bundle_path (); char path [1024]; - if (!xamarin_locate_assembly_resource_for_root (app_path, NULL, xamarin_runtime_configuration_name, path, sizeof (path))) { - LOG (PRODUCT ": Could not locate the runtime config file '%s' in the app bundle: %s\n", xamarin_runtime_configuration_name, app_path); + if (!xamarin_locate_app_resource (xamarin_runtime_configuration_name, path, sizeof (path))) { + LOG (PRODUCT ": Could not locate the runtime config file '%s' in the app bundle.\n", xamarin_runtime_configuration_name); return; } diff --git a/runtime/runtime.m b/runtime/runtime.m index bb64d9163018..69180f940f38 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -2497,6 +2497,13 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags; name [len - 4] = 0; // strip off any extensions. } +bool +xamarin_locate_app_resource (const char *resource, char *path, size_t pathlen) +{ + const char *app_path = xamarin_get_bundle_path (); + return xamarin_locate_assembly_resource_for_root (app_path, NULL, resource, path, pathlen); +} + static bool xamarin_locate_assembly_resource_for_root (const char *root, const char *culture, const char *resource, char *path, size_t pathlen) { diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index 09cbfe5e9f62..bb85b9a29d28 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -272,6 +272,7 @@ void xamarin_get_assembly_name_without_extension (const char *aname, char *nam bool xamarin_locate_assembly_resource_for_name (MonoAssemblyName *assembly_name, const char *resource, char *path, size_t pathlen); bool xamarin_locate_assembly_resource_for_root (const char *root, const char *culture, const char *resource, char *path, size_t pathlen); bool xamarin_locate_assembly_resource (const char *assembly_name, const char *culture, const char *resource, char *path, size_t pathlen); +bool xamarin_locate_app_resource (const char *resource, char *path, size_t pathlen); // this functions support NSLog/NSString-style format specifiers. void xamarin_printf (const char *format, ...); From 608dfb37bc5fed3e1d454b7bbfe2d5e24ac7bb13 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 23 Jun 2021 09:46:54 +0200 Subject: [PATCH 28/42] [dotnet] Make the globalization data file an architecture-specific file. This was adapted from https://github.com/xamarin/xamarin-macios/pull/11320. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 12 +++++++++++- runtime/runtime.m | 14 +++++++++++++- tools/dotnet-linker/LinkerConfiguration.cs | 4 ++++ tools/dotnet-linker/Steps/GenerateMainStep.cs | 5 +++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 1a1e602e7f29..9a99d2b956df 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -138,6 +138,10 @@ false + + <_GlobalizationDataFile Condition="'$(_PlatformName)' != 'macOS' And '$(InvariantGlobalization)' != 'true' And '$(_GlobalizationDataFile)' == ''">icudt.dat + + true @@ -298,6 +302,8 @@ <_ArchitectureSpecificFiles Include="$(_RuntimeConfigurationFile)" Condition="'$(GenerateRuntimeConfigurationFiles)' != ''" /> + + <_ArchitectureSpecificFiles Include="$(_GlobalizationDataFile)" Condition="'$(_GlobalizationDataFile)' != ''" /> + + + <_CustomLinkerOptionsFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)custom-linker-options.txt')) @@ -348,6 +357,7 @@ DeploymentTarget=$(_MinimumOSVersion) @(_BundlerEnvironmentVariables -> 'EnvironmentVariable=%(Identity)=%(Value)') @(_XamarinFrameworkAssemblies -> 'FrameworkAssembly=%(Filename)') + GlobalizationDataFile=$(_GlobalizationDataFile) Interpreter=$(MtouchInterpreter) IntermediateLinkDir=$(IntermediateLinkDir) InvariantGlobalization=$(InvariantGlobalization) @@ -901,7 +911,7 @@ + Condition="'$(_PlatformName)' != 'macOS' And '$(InvariantGlobalization)' != 'true' And '%(Filename)%(Extension)' == '$(_GlobalizationDataFile)'" /> diff --git a/runtime/runtime.m b/runtime/runtime.m index 69180f940f38..1c5a0f1cc0e6 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -55,6 +55,9 @@ #endif int xamarin_log_level = 0; const char *xamarin_executable_name = NULL; +#if DOTNET +const char *xamarin_icu_dat_file_name = NULL; +#endif #if MONOMAC || TARGET_OS_MACCATALYST NSString * xamarin_custom_bundle_name = @"MonoBundle"; #endif @@ -2384,6 +2387,15 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags; xamarin_vm_initialize () { char *pinvokeOverride = xamarin_strdup_printf ("%p", &xamarin_pinvoke_override); + char *icu_dat_file_path = NULL; + + char path [1024]; + if (!xamarin_locate_app_resource (xamarin_icu_dat_file_name, path, sizeof (path))) { + LOG (PRODUCT ": Could not locate the ICU data file '%s' in the app bundle.\n", xamarin_icu_dat_file_name); + } else { + icu_dat_file_path = path; + } + // All the properties we pass here must also be listed in the _RuntimeConfigReservedProperties item group // for the _CreateRuntimeConfiguration target in dotnet/targets/Xamarin.Shared.Sdk.targets. const char *propertyKeys[] = { @@ -2394,7 +2406,7 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags; const char *propertyValues[] = { xamarin_get_bundle_path (), pinvokeOverride, - "icudt.dat", + icu_dat_file_path, }; static_assert (sizeof (propertyKeys) == sizeof (propertyValues), "The number of keys and values must be the same."); diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index aff36666e15f..0e5635d5ed71 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -21,6 +21,7 @@ public class LinkerConfiguration { public string CacheDirectory { get; private set; } public Version DeploymentTarget { get; private set; } public HashSet FrameworkAssemblies { get; private set; } = new HashSet (); + public string GlobalizationDataFile { get; private set; } public string IntermediateLinkDir { get; private set; } public bool InvariantGlobalization { get; private set; } public string ItemsDirectory { get; private set; } @@ -232,6 +233,9 @@ public static LinkerConfiguration GetInstance (LinkContext context, bool createI throw new InvalidOperationException ($"Invalid XamarinRuntime '{value}' in {linker_file}"); Application.XamarinRuntime = rv; break; + case "GlobalizationDataFile": + GlobalizationDataFile = value; + break; case "InvariantGlobalization": InvariantGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); break; diff --git a/tools/dotnet-linker/Steps/GenerateMainStep.cs b/tools/dotnet-linker/Steps/GenerateMainStep.cs index d0e7536728a6..ef0cbb04b31f 100644 --- a/tools/dotnet-linker/Steps/GenerateMainStep.cs +++ b/tools/dotnet-linker/Steps/GenerateMainStep.cs @@ -29,10 +29,15 @@ protected override void TryEndProcess () var contents = new StringBuilder (); contents.AppendLine ("#include "); + contents.AppendLine (); + contents.AppendLine ("extern \"C\" const char *xamarin_icu_dat_file_name;"); + contents.AppendLine (); contents.AppendLine ("static void xamarin_initialize_dotnet ()"); contents.AppendLine ("{"); if (Configuration.InvariantGlobalization) { contents.AppendLine ("\tsetenv (\"DOTNET_SYSTEM_GLOBALIZATION_INVARIANT\", \"1\", 1);"); + } else { + contents.AppendLine ($"\txamarin_icu_dat_file_name = \"{Configuration.GlobalizationDataFile}\";"); } contents.AppendLine ("}"); contents.AppendLine (); From a78be0af4e0119effbda87b75803a3cf572e598b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 23 Jun 2021 16:52:13 +0200 Subject: [PATCH 29/42] [dotnet] Ignore Info.plist files from the input app bundles when merging app bundles. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 1 + .../Tasks/MergeAppBundlesTaskBase.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 9a99d2b956df..829e7f629b3d 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -310,6 +310,7 @@ SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" ArchitectureSpecificFiles="@(_ArchitectureSpecificFiles)" + IgnoreFiles="Info.plist" InputAppBundles="@(_AssemblyPublishDirectories)" OutputAppBundle="$(MSBuildProjectDirectory)/$(_AppBundlePath)" SdkDevPath="$(_SdkDevPath)" diff --git a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs index a94ab93a0acf..5ab7fd6f1916 100644 --- a/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks.Core/Tasks/MergeAppBundlesTaskBase.cs @@ -31,6 +31,10 @@ public abstract partial class MergeAppBundlesTaskBase : XamarinTask { // 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; } @@ -324,6 +328,18 @@ bool MergeAppBundles () } } + // 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 (); From e68831b56083ad28a28171b0bdfcd1b62965fda6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 23 Jun 2021 16:53:06 +0200 Subject: [PATCH 30/42] [dotnet] Compile the app manifest in the outer build for multi-rid apps. We're already not copying the Info.plists from the input app bundles. The Info.plist file is computed differently, depending on the architecture(s), which means we only know enough to compute it when building the outer app bundle. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 829e7f629b3d..a858befb634b 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -188,6 +188,7 @@ _RunRidSpecificBuild; _CompileEntitlements; + _CompileAppManifest; _CreateMergedAppBundle; Codesign; From 7f505c35d5224b11cd6124fdec36378b78fcc9eb Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 23 Jun 2021 16:53:45 +0200 Subject: [PATCH 31/42] [dotnet] Rework how we compute the TargetArchitectures property. Add a _MapRuntimeIdentifierToTargetArchitecture target that computes the TargetArchitectures property from either the RuntimeIdentifier or the RuntimeIdentifiers properties. Also make sure this target is executed before _ComputeTargetArchitectures. This is required so that we have a correct TargetArchitectures value for multi-rid builds when compiling the app manifest (Info.plist). --- .../Xamarin.Shared.Sdk.DefaultItems.targets | 53 ++++++++++--------- dotnet/targets/Xamarin.Shared.Sdk.targets | 5 +- msbuild/Xamarin.Shared/Xamarin.Shared.props | 4 ++ msbuild/Xamarin.Shared/Xamarin.Shared.targets | 10 +++- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets index fd8eb2e1a3a9..dc398a10936d 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets @@ -31,33 +31,38 @@ - - - - ARM64 - x86_64 - i386 - ARMv7 - - - ARM64 - x86_64 - - - i386 - ARM64_32 - - - x86_64 - ARM64 - - - 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.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index a858befb634b..02cb4cb018a0 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -257,7 +257,10 @@ Returns="@(_AssemblyPublishDirectory)" > - <_AssemblyPublishDirectory Include="$(MSBuildProjectDirectory)/$(_AppBundlePath)" RuntimeIdentifier="$(RuntimeIdentifier)" /> + <_AssemblyPublishDirectory Include="$(MSBuildProjectDirectory)/$(_AppBundlePath)"> + $(RuntimeIdentifier) + $(TargetArchitectures) + 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; + + + + - + + _CreateDebugSettings; _CreateDebugConfiguration; $(CreateAppBundleDependsOn); From 1fa1cf36506f35657b933700424bb03d0cefcd4d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 24 Jun 2021 11:59:22 +0200 Subject: [PATCH 35/42] [dotnet] Use the correct relative paths for Info.plist for all platforms. Fixes this error: > Unable to merge the file 'Contents/Info.plist', it's different between the input app bundles. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 3d7eca4b3d29..7caaa25a226c 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -314,7 +314,7 @@ SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" ArchitectureSpecificFiles="@(_ArchitectureSpecificFiles)" - IgnoreFiles="Info.plist" + IgnoreFiles="$(_AppBundleManifestRelativePath)Info.plist" InputAppBundles="@(_AssemblyPublishDirectories)" OutputAppBundle="$(MSBuildProjectDirectory)/$(_AppBundlePath)" SdkDevPath="$(_SdkDevPath)" From 82fc13efbc7282e82e41da4fbf6f819df0e791d6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 24 Jun 2021 14:16:14 +0200 Subject: [PATCH 36/42] [tests] Fix assertions regarding errors after recent changes. --- .../TaskTests/MergeAppBundleTaskTest.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index fa90d1950bc5..e0a802c46e01 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -153,8 +153,10 @@ public void TestDifferentOtherFiles () var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); var task = CreateTask (outputBundle, bundles); Assert.IsFalse (task.Execute (), "Task execution"); - Assert.AreEqual (1, Engine.Logger.ErrorEvents.Count, "Errors"); - Assert.AreEqual ("Unable to merge the file 'Something.txt', it's different between the input app bundles.", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error + 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] @@ -205,8 +207,10 @@ public void TestSymlinksWithDifferentTargets () var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app"); var task = CreateTask (outputBundle, bundleA, bundleB); Assert.IsFalse (task.Execute (), "Task execution"); - Assert.AreEqual (1, Engine.Logger.ErrorEvents.Count, "Errors"); - Assert.AreEqual ("Can't merge the symlink 'B.txt', it has different targets.", Engine.Logger.ErrorEvents [0].Message, "Error message"); // FIXME: error + 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] From 9a406d2e9bf194bb3036b4cff41f5e0800a6ad54 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 24 Jun 2021 14:22:08 +0200 Subject: [PATCH 37/42] [tests] Fix makefile for ComplexAssembly to do nothing on a rebuild. * Don't use a fake target, it'll always be executed. --- tests/common/TestProjects/ComplexAssembly/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/common/TestProjects/ComplexAssembly/Makefile b/tests/common/TestProjects/ComplexAssembly/Makefile index e33d1331496a..da40ab9516fa 100644 --- a/tests/common/TestProjects/ComplexAssembly/Makefile +++ b/tests/common/TestProjects/ComplexAssembly/Makefile @@ -2,11 +2,11 @@ TOP=../../../.. include $(TOP)/Make.config -prepare: +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) prepare +.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 From 2f65937d27f9306191b473866d61563d243f7e3a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 24 Jun 2021 14:23:23 +0200 Subject: [PATCH 38/42] [tests] Remove BOM in new file. --- .../TaskTests/MergeAppBundleTaskTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index e0a802c46e01..3fd9b2ee8528 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Collections.Generic; From 3497373b4d0666090144ff64a1a0ed0c6617499b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 24 Jun 2021 14:40:41 +0200 Subject: [PATCH 39/42] [dotnet] Compute 'ComputedPlatform' correctly for outer multi-rid builds as well. --- dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets index dc398a10936d..d73dba50929e 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets @@ -62,7 +62,7 @@ - iPhoneSimulator + iPhoneSimulator iPhone From 31b48eb8a1848ce9dfcfe0f2d852dc75e9b5cbbc Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 24 Jun 2021 17:16:50 +0200 Subject: [PATCH 40/42] [tests] Copy more supporting files for building monotouch-test with .NET. --- tests/dotnet/UnitTests/ProjectTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index e569bbc72d5c..47d0e2bae57c 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -436,6 +436,7 @@ public void BuildFatMonoTouchTest (ApplePlatform platform, string runtimeIdentif 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); From 6d6db24de4e553357aaf134bda683071a04e1f30 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 24 Jun 2021 17:39:39 +0200 Subject: [PATCH 41/42] [dotnet] Use relative paths in a few places. To not cause problems on Windows. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 7caaa25a226c..f63fe31041d6 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -257,7 +257,7 @@ Returns="@(_AssemblyPublishDirectory)" > - <_AssemblyPublishDirectory Include="$(MSBuildProjectDirectory)/$(_AppBundlePath)"> + <_AssemblyPublishDirectory Include="$(_AppBundlePath)"> $(RuntimeIdentifier) $(TargetArchitectures) @@ -316,7 +316,7 @@ ArchitectureSpecificFiles="@(_ArchitectureSpecificFiles)" IgnoreFiles="$(_AppBundleManifestRelativePath)Info.plist" InputAppBundles="@(_AssemblyPublishDirectories)" - OutputAppBundle="$(MSBuildProjectDirectory)/$(_AppBundlePath)" + OutputAppBundle="$(_AppBundlePath)" SdkDevPath="$(_SdkDevPath)" TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)" /> From 8aff15a4f0f5c76432ee0c140f1d300b356b5d23 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 25 Jun 2021 08:26:01 +0200 Subject: [PATCH 42/42] [tests] Don't run parallel make in tests/common/TestProjects/ComplexAssembly, it may cause make to deadlock. --- .../TaskTests/MergeAppBundleTaskTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index 3fd9b2ee8528..40e02d6d90e2 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -27,12 +27,11 @@ public void SetUp () RunMake (Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly"), environment: env); } - static void RunMake (string directory, Dictionary environment = null, int j = 8) + static void RunMake (string directory, Dictionary environment = null) { var arguments = new List { "-C", directory, - $"-j{j}", "V=1", }; var rv = ExecutionHelper.Execute ("make",