CoreCLR runtime tests + Mono on the x64 iOS simulator (#43954)
authorimhameed <imhameed@microsoft.com>
Tue, 3 Aug 2021 22:49:26 +0000 (18:49 -0400)
committerGitHub <noreply@github.com>
Tue, 3 Aug 2021 22:49:26 +0000 (15:49 -0700)
This creates another `runtime-staging` lane, named "Build iOSSimulator x64
Release AllSubsets_Mono_RuntimeTests", that will eventually run the runtime
test suite against Mono's non-LLVM JIT on the iOS simulator on amd64 hosts.
Failing tests are added to the exclusion lists in issues.targets.

The tests aren't set to run yet, because they currently take a very long time
to execute.

`AppleAppBuilder` no longer requires a `MainLibraryFileName`. If omitted, one
must be supplied when the app bundle is launched via an environment variable
named `MONO_APPLE_APP_ENTRY_POINT_LIB_NAME`. The generated apps also accept
another environment variable named `MONO_APPLE_APP_ASSEMBLY_LOAD_PREFIX`, which
is a hack used to allow assembly lookup to proceed in a nested app-relative
subdirectory before falling back to the root of the app bundle. This is
necessary because app bundles contain multiple individual test assemblies, and
these assemblies sometimes have dependencies with names that collide with the
dependencies of other test assemblies inside the bundle.

13 files changed:
eng/pipelines/coreclr/templates/helix-queues-setup.yml
eng/pipelines/runtime-staging.yml
src/tasks/AppleAppBuilder/AppleAppBuilder.cs
src/tasks/AppleAppBuilder/Templates/runtime.m
src/tests/Common/CLRTest.Execute.Bash.targets
src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs
src/tests/Common/helixpublishwitharcade.proj
src/tests/Common/tests.targets
src/tests/Directory.Build.targets
src/tests/Interop/ICastable/Castable.csproj
src/tests/build.sh
src/tests/issues.targets
src/tests/run.proj

index e6a4132..2b3a80c 100644 (file)
@@ -26,18 +26,22 @@ jobs:
     runtimeFlavorDisplayName: ${{ parameters.runtimeFlavorDisplayName }}
     helixQueues:
 
+    # iOS/tvOS simulator x64/x86
+    - ${{ if in(parameters.platform, 'iOSSimulator_x64', 'tvOSSimulator_x64') }}:
+      - OSX.1015.Amd64.Open
+
     # Android arm64
     - ${{ if in(parameters.platform, 'Android_arm64') }}:
       - Windows.10.Amd64.Android.Open
-    
+
     # Android x64
     - ${{ if in(parameters.platform, 'Android_x64') }}:
       - Ubuntu.1804.Amd64.Android.Open
-    
+
     # Browser wasm
     - ${{ if eq(parameters.platform, 'Browser_wasm') }}:
       - Ubuntu.1804.Amd64.Open
-    
+
     # Linux arm
     - ${{ if eq(parameters.platform, 'Linux_arm') }}:
       - ${{ if eq(variables['System.TeamProject'], 'public') }}:
index 27843c8..92e672c 100644 (file)
@@ -260,6 +260,48 @@ jobs:
         testRunNamePrefixSuffix: Mono_$(_BuildConfig)
 
 #
+# Build the whole product using Mono and run runtime tests with the JIT.
+#
+- template: /eng/pipelines/common/platform-matrix.yml
+  parameters:
+    jobTemplate: /eng/pipelines/common/global-build-job.yml
+    helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml
+    buildConfig: Release
+    runtimeFlavor: mono
+    platforms:
+    - iOSSimulator_x64
+    variables:
+      - ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}:
+        - name: _HelixSource
+          value: pr/dotnet/runtime/$(Build.SourceBranch)
+      - ${{ if and(eq(variables['System.TeamProject'], 'public'), ne(variables['Build.Reason'], 'PullRequest')) }}:
+        - name: _HelixSource
+          value: ci/dotnet/runtime/$(Build.SourceBranch)
+      - name: timeoutPerTestInMinutes
+        value: 60
+      - name: timeoutPerTestCollectionInMinutes
+        value: 180
+    jobParameters:
+      testGroup: innerloop
+      nameSuffix: AllSubsets_Mono_RuntimeTests
+      buildArgs: -s mono+libs -c $(_BuildConfig)
+      timeoutInMinutes: 240
+      condition: >-
+        or(
+          eq(dependencies.evaluate_paths.outputs['SetPathVars_runtimetests.containsChange'], true),
+          eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true),
+          eq(variables['isFullMatrix'], true))
+      # Test execution is temporarily disabled because test apps no longer launch
+      # and the test suite times out after two hours, even if xharness cannot
+      # successfully launch any tests. Re-enable once these issues have been fixed.
+      #
+      # extra steps, run tests
+      # extraStepsTemplate: /eng/pipelines/common/templates/runtimes/android-runtime-and-send-to-helix.yml
+      # extraStepsParameters:
+      #   creator: dotnet-bot
+      #   testRunNamePrefixSuffix: Mono_$(_BuildConfig)
+
+#
 # Build the whole product using Mono for Android and run runtime tests with Android devices
 #
 - template: /eng/pipelines/common/platform-matrix.yml
index 6183fe0..9127192 100644 (file)
@@ -50,9 +50,11 @@ public class AppleAppBuilderTask : Task
     public string MonoRuntimeHeaders { get; set; } = ""!;
 
     /// <summary>
-    /// This library will be used as an entry-point (e.g. TestRunner.dll)
+    /// This library will be used as an entry point (e.g. TestRunner.dll). Can
+    /// be empty. If empty, the entry point of the app must be specified in an
+    /// environment variable named "MONO_APPLE_APP_ENTRY_POINT_LIB_NAME" when
+    /// running the resulting app.
     /// </summary>
-    [Required]
     public string MainLibraryFileName { get; set; } = ""!;
 
     /// <summary>
@@ -155,9 +157,12 @@ public class AppleAppBuilderTask : Task
     {
         bool isDevice = (TargetOS == TargetNames.iOS || TargetOS == TargetNames.tvOS);
 
-        if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName)))
+        if (!string.IsNullOrEmpty(MainLibraryFileName))
         {
-            throw new ArgumentException($"MainLibraryFileName='{MainLibraryFileName}' was not found in AppDir='{AppDir}'");
+            if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName)))
+            {
+                throw new ArgumentException($"MainLibraryFileName='{MainLibraryFileName}' was not found in AppDir='{AppDir}'");
+            }
         }
 
         if (ProjectName.Contains(' '))
index de9ca63..3146f05 100644 (file)
@@ -86,13 +86,33 @@ free_aot_data (MonoAssembly *assembly, int size, void *user_data, void *handle)
     munmap (handle, size);
 }
 
-static MonoAssembly*
+static const char *assembly_load_prefix = NULL;
+
+static MonoAssembly *
+load_assembly_aux (const char *filename, const char *culture, const char *bundle)
+{
+    char path [1024];
+    int res;
+    if (culture && strcmp (culture, ""))
+        res = snprintf (path, sizeof (path) - 1, "%s/%s/%s", bundle, culture, filename);
+    else
+        res = snprintf (path, sizeof (path) - 1, "%s/%s", bundle, filename);
+    assert (res > 0);
+
+    struct stat buffer;
+    if (stat (path, &buffer) == 0) {
+        MonoAssembly *assembly = mono_assembly_open (path, NULL);
+        assert (assembly);
+        return assembly;
+    }
+    return NULL;
+}
+
+static MonoAssembly *
 load_assembly (const char *name, const char *culture)
 {
     const char *bundle = get_bundle_path ();
     char filename [1024];
-    char path [1024];
-    int res;
 
     os_log_info (OS_LOG_DEFAULT, "assembly_preload_hook: %{public}s %{public}s %{public}s\n", name, culture, bundle);
 
@@ -105,19 +125,14 @@ load_assembly (const char *name, const char *culture)
         strlcat (filename, ".dll", sizeof (filename));
     }
 
-    if (culture && strcmp (culture, ""))
-        res = snprintf (path, sizeof (path) - 1, "%s/%s/%s", bundle, culture, filename);
-    else
-        res = snprintf (path, sizeof (path) - 1, "%s/%s", bundle, filename);
-    assert (res > 0);
-
-    struct stat buffer;
-    if (stat (path, &buffer) == 0) {
-        MonoAssembly *assembly = mono_assembly_open (path, NULL);
-        assert (assembly);
-        return assembly;
+    if (assembly_load_prefix [0] != '\0') {
+        char prefix_bundle [1024];
+        int res = snprintf (prefix_bundle, sizeof (prefix_bundle) - 1, "%s/%s", bundle, assembly_load_prefix);
+        assert (res > 0);
+        MonoAssembly *ret = load_assembly_aux (filename, culture, prefix_bundle);
+        if (ret) return ret;
     }
-    return NULL;
+    return load_assembly_aux (filename, culture, bundle);
 }
 
 static MonoAssembly*
@@ -260,7 +275,7 @@ mono_ios_runtime_init (void)
 
     // TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES
     const char *appctx_keys [] = {
-        "RUNTIME_IDENTIFIER", 
+        "RUNTIME_IDENTIFIER",
         "APP_CONTEXT_BASE_DIRECTORY",
 #if !defined(INVARIANT_GLOBALIZATION)
         "ICU_DAT_FILE_PATH"
@@ -291,6 +306,19 @@ mono_ios_runtime_init (void)
         free (file_path);
     }
 
+    const char* executable = "%EntryPointLibName%";
+    if (executable [0] == '\0') {
+        executable = getenv ("MONO_APPLE_APP_ENTRY_POINT_LIB_NAME");
+    }
+    if (executable == NULL) {
+        executable = "";
+    }
+
+    assembly_load_prefix = getenv ("MONO_APPLE_APP_ASSEMBLY_LOAD_PREFIX");
+    if (assembly_load_prefix == NULL) {
+        assembly_load_prefix = "";
+    }
+
     monovm_initialize (sizeof (appctx_keys) / sizeof (appctx_keys [0]), appctx_keys, appctx_values);
 
 #if (FORCE_INTERPRETER && !FORCE_AOT)
@@ -334,7 +362,6 @@ mono_ios_runtime_init (void)
     MONO_EXIT_GC_UNSAFE;
 #endif
 
-    const char* executable = "%EntryPointLibName%";
     MonoAssembly *assembly = load_assembly (executable, NULL);
     assert (assembly);
     os_log_info (OS_LOG_DEFAULT, "Executable: %{public}s", executable);
index 7031b45..ee375b8 100644 (file)
@@ -11,7 +11,7 @@ This file contains the logic for providing Execution Script generation.
 WARNING:   When setting properties based on their current state (for example:
            <Foo Condition="'$(Foo)'==''>Bar</Foo>).  Be very careful.  Another script generation
            target might be trying to do the same thing.  It's better to avoid this by instead setting a new property.
-           
+
            Additionally, be careful with itemgroups.  Include will propagate outside of the target too!
 
 ***********************************************************************************************
@@ -21,14 +21,14 @@ WARNING:   When setting properties based on their current state (for example:
   <!-- This is here because of this bug: https://docs.microsoft.com/en-us/archive/blogs/msbuild/well-known-limitation-dynamic-items-and-properties-not-emitted-until-target-execution-completes -->
   <Target Name="FetchExternalPropertiesForXplat">
     <!--Call GetExecuteShFullPath to get ToRunProject cmd file Path  -->
-    <MSBuild Projects="$(CLRTestProjectToRun)" 
+    <MSBuild Projects="$(CLRTestProjectToRun)"
              Targets="GetExecuteShFullPath"
              Properties="GenerateRunScript=True"
              Condition="'$(_CLRTestNeedsProjectToRun)' == 'True'">
       <Output TaskParameter="TargetOutputs" PropertyName="_CLRTestToRunFileFullPath"/>
     </MSBuild>
   </Target>
-  
+
   <!--
     Target: GetExecuteShFullPath
     Return Executed Sh Relative Full Path
@@ -53,11 +53,11 @@ WARNING:   When setting properties based on their current state (for example:
   <!--
   *******************************************************************************************
   TARGET: GenerateExecutionScriptInternal
-  
+
   For tests that "run" we will generate an execution script that wraps any arguments or other
   goo.  This allows generated .lst files to be very simple and reusable to invoke any "stage"
   of test execution.
-  
+
   Notice this is hooked up to run after targets that generate the stores that are marked with GenerateScripts metadata.
   Note also that this means it will run after the first of such targets.
   -->
@@ -124,11 +124,11 @@ fi
 if [ -z ${CLRTestExpectedExitCode+x} ]%3B then export CLRTestExpectedExitCode=$(CLRTestExitCode)%3B fi
 echo BEGIN EXECUTION]]>
       </BashCLRTestExitCodePrep>
-    
+
       <BashCLRTestArgPrep Condition=" '$(CLRTestExecutionArguments)'!='' ">
 <![CDATA[if [ -z ${CLRTestExecutionArguments+x} ]%3B then CLRTestExecutionArguments=($(CLRTestExecutionArguments))%3B fi]]>
       </BashCLRTestArgPrep>
-    
+
       <!-- By default, be prepared to do a full check -->
       <BashCLRTestExitCodeCheck><![CDATA[
 echo Expected: $CLRTestExpectedExitCode
@@ -150,7 +150,7 @@ else
 fi
       ]]></BashCLRTestExitCodeCheck>
     </PropertyGroup>
-  
+
     <ItemGroup Condition="$(_CLRTestNeedsToRun)">
       <Clean Include="$(OutputPath)\$(MSBuildProjectName).sh"/>
 
@@ -188,29 +188,29 @@ ReflectionRoots=
 
 shopt -s nullglob
 
-if [ ! -z "$DoLink" ]; 
+if [ ! -z "$DoLink" ];
 then
-  if [ ! -x "$ILLINK" ]; 
+  if [ ! -x "$ILLINK" ];
   then
     echo "Illink executable [$ILLINK] Invalid"
     exit 1
   fi
-  
+
   # Clean up old Linked binaries, if any
   rm -rf $LinkBin
-    
+
   # Remove Native images, since the goal is to run from Linked binaries
   rm -f *.ni.*
 
   # Use hints for reflection roots, if provided in $(ReflectionRootsXml)
-  if [ -f $(ReflectionRootsXml) ]; 
+  if [ -f $(ReflectionRootsXml) ];
   then
     ReflectionRoots="-x $(ReflectionRootsXml)"
   fi
 
   # Include all .exe files in this directory as entry points (some tests had multiple .exe file modules)
-  for bin in *.exe *.dll; 
-  do 
+  for bin in *.exe *.dll;
+  do
     Assemblies="$Assemblies -a ${bin%.*}"
   done
 
@@ -224,14 +224,14 @@ then
   if [  $ERRORLEVEL -ne 0 ]
   then
     echo ILLINK FAILED $ERRORLEVEL
-    if [ -z "$KeepLinkedBinaries" ]; 
+    if [ -z "$KeepLinkedBinaries" ];
     then
       rm -rf $LinkBin
     fi
     exit 1
   fi
-  
-  # Copy CORECLR native binaries to $LinkBin, 
+
+  # Copy CORECLR native binaries to $LinkBin,
   # so that we can run the test based on that directory
   cp $CORE_ROOT/*.so $LinkBin/
   cp $CORE_ROOT/corerun $LinkBin/
@@ -252,9 +252,9 @@ fi
 # Clean up the LinkBin directories after test execution.
 # Otherwise, RunTests may run out of disk space.
 
-if [ ! -z "$DoLink" ]; 
+if [ ! -z "$DoLink" ];
 then
-  if [ -z "$KeepLinkedBinaries" ]; 
+  if [ -z "$KeepLinkedBinaries" ];
   then
     rm -rf $LinkBin
   fi
@@ -364,7 +364,43 @@ fi
 $__Command $HARNESS_RUNNER android run --instrumentation="net.dot.MonoRunner" --package-name="net.dot.$__Category" --output-directory="$__OutputDir" --arg=entrypoint:libname=$(MsBuildProjectName).dll --expected-exit-code=100 -v
 CLRTestExitCode=$?
 
-# Exist code of xharness is zero when tests finished successfully
+# Exit code of xharness is zero when tests finished successfully
+CLRTestExpectedExitCode=0
+      ]]>
+      </BashCLRTestLaunchCmds>
+      <BashCLRTestLaunchCmds Condition="'$(CLRTestKind)' == 'BuildAndRun' And $(TargetOS) == 'iOSSimulator' ">
+      <![CDATA[
+__Command=""
+if [ ! -z ${__TestDotNetCmd+x} ] %3B then
+  __Command+=" $__TestDotNetCmd"
+else
+  __Command+=" dotnet"
+fi
+
+if [ ! -z "$XHARNESS_CLI_PATH" ]; then
+       # When running in CI, we only have the .NET runtime available
+       # We need to call the XHarness CLI DLL directly via dotnet exec
+       HARNESS_RUNNER="exec $XHARNESS_CLI_PATH"
+else
+       HARNESS_RUNNER="xharness"
+fi
+
+xcode_path="%24(dirname "%24(dirname "%24(xcode-select -p)")")"
+simulator_app="$xcode_path/Contents/Developer/Applications/Simulator.app"
+open -a "$simulator_app"
+helix_runner_uid="%24(id -u)"
+
+sudo launchctl asuser "$helix_runner_uid" $__Command $HARNESS_RUNNER apple just-run %5c
+  --app="net.dot.$__Category" %5c
+  --output-directory="$__OutputDir" %5c
+  --set-env="MONO_APPLE_APP_ENTRY_POINT_LIB_NAME=testdir-$(MsBuildProjectName)/$(MsBuildProjectName).dll" %5c
+  --set-env="MONO_APPLE_APP_ASSEMBLY_LOAD_PREFIX=testdir-$(MsBuildProjectName)" %5c
+  --expected-exit-code=100 %5c
+  --targets ios-simulator-64 %5c
+  -v
+CLRTestExitCode=$?
+
+# Exit code of xharness is zero when tests finished successfully
 CLRTestExpectedExitCode=0
       ]]>
       </BashCLRTestLaunchCmds>
@@ -383,17 +419,17 @@ CLRTestExpectedExitCode=0
 @(CLRTestBashEnvironmentVariable -> '%(Identity)', '%0a')
       </BashEnvironmentVariables>
     </PropertyGroup>
-     
+
     <Message Text="MSBuildProjectDirectory:$(MSBuildProjectDirectory)" />
     <Message Text="_CLRTestToRunFileFullPath:$(_CLRTestToRunFileFullPath)"/>
     <Message Text="_CLRTestRunFile:$(_CLRTestRunFile)" />
-    
+
     <ItemGroup>
       <_RequiredProperties Include="_CLRTestRunFile">
         <Value>$(_CLRTestRunFile)</Value>
       </_RequiredProperties>
     </ItemGroup>
-    
+
     <!-- Raise an error if any value in _RequiredProperties is missing  -->
     <Error Condition=" '%(_RequiredProperties.Value)'=='' "
       Text="Missing required test property [%(_RequiredProperties.Identity)]. Something isn't plumbed through correctly.  Contact $(_CLRTestBuildSystemOwner)." />
@@ -407,7 +443,7 @@ CLRTestExpectedExitCode=0
 usage()
 {
     echo "Usage: $0  $(_CLRTestParamList)"
-    echo 
+    echo
     echo "Arguments:"
 @(BashCLRTestExecutionScriptArgument -> '    echo "-%(Identity)=%(ParamName)"
     echo      "%(Description)"', '
@@ -498,14 +534,14 @@ $(BashCLRTestExitCodeCheck)
     </PropertyGroup>
 
     <!-- Write the file.
-         Note: under the hood, this will rely on Environment.NewLine for line 
+         Note: under the hood, this will rely on Environment.NewLine for line
          endings. This means that if the scripts are being generated on Windows,
-         the line endings will need to be changed from CR-LF to Unix (LF) line 
+         the line endings will need to be changed from CR-LF to Unix (LF) line
          endings before running the scripts on Unix platforms. -->
     <WriteLinesToFile
       File="$(OutputPath)\$(MSBuildProjectName).sh"
       Lines="$(_CLRTestExecutionScriptText)"
       Overwrite="true" />
   </Target>
-  
+
 </Project>
index f4d179c..7a58262 100644 (file)
@@ -81,6 +81,11 @@ namespace CoreclrTestLib
                         }
                     }
 
+                    if (platform != "android")
+                    {
+                        cmdStr += " --target ios-simulator-64";
+                    }
+
                     using (Process process = new Process())
                     {
                         if (OperatingSystem.IsWindows())
@@ -97,6 +102,8 @@ namespace CoreclrTestLib
                         process.StartInfo.RedirectStandardOutput = true;
                         process.StartInfo.RedirectStandardError = true;
 
+                        outputWriter.WriteLine("XXXih: cmdStr = {0}", cmdStr);
+                        errorWriter.WriteLine("XXXih: cmdStr = {0}", cmdStr);
                         DateTime startTime = DateTime.Now;
                         process.Start();
 
@@ -125,7 +132,7 @@ namespace CoreclrTestLib
                                     cmdStr, timeout, startTime.ToString(), endTime.ToString());
                             errorWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}, start: {2}, end: {3})",
                                     cmdStr, timeout, startTime.ToString(), endTime.ToString());
-                            
+
                             process.Kill(entireProcessTree: true);
                         }
                     }
@@ -152,7 +159,7 @@ namespace CoreclrTestLib
             {
                 cmdPrefix = "-c";
             }
-            
+
             return $"{cmdPrefix} \"{cmd}\"";
         }
     }
index b98bd8e..349530a 100644 (file)
@@ -83,6 +83,7 @@
     <HelixRuntimeRid Condition="'$(TargetOSSpec)' == 'Linux_musl'">linux-musl-$(TargetArchitecture)</HelixRuntimeRid>
     <HelixRuntimeRid Condition="'$(TargetOSSpec)' == 'Browser'">browser-wasm</HelixRuntimeRid>
     <HelixRuntimeRid Condition="'$(TargetOSSpec)' == 'Android'">android-$(TargetArchitecture)</HelixRuntimeRid>
+    <HelixRuntimeRid Condition="'$(TargetOSSpec)' == 'iOSSimulator'">iossimulator-$(TargetArchitecture)</HelixRuntimeRid>
   </PropertyGroup>
 
   <PropertyGroup>
     <DotNetCliRuntime Condition=" '$(TargetArchitecture)' == 'arm64' ">win-x64</DotNetCliRuntime>
   </PropertyGroup>
 
-  <PropertyGroup Condition="'$(TargetOS)' == 'Android'">
+  <PropertyGroup Condition="'$(TargetOS)' == 'iOSSimulator'">
+    <DotNetCliPackageType>sdk</DotNetCliPackageType>
+    <GlobalJsonContent>$([System.IO.File]::ReadAllText('$(RepoRoot)global.json'))</GlobalJsonContent>
+    <DotNetCliVersion>$([System.Text.RegularExpressions.Regex]::Match($(GlobalJsonContent), '(%3F&lt;="dotnet": ").*(%3F=")'))</DotNetCliVersion>
+    <!-- iOSSimulator needs to use the host OS DotnetCliRuntime -->
+    <DotNetCliRuntime>osx-x64</DotNetCliRuntime>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(TargetOS)' == 'Android' or '$(TargetOS)' == 'iOS' or '$(TargetOS)' == 'iOSSimulator'">
     <IncludeXHarnessCli>true</IncludeXHarnessCli>
   </PropertyGroup>
 
       <!-- If there is a TestGrouping, then take only the files that belong to the TestGroup == $(_PayloadGroup). -->
       <_PayloadFiles Include="@(_TestGroupingRelevant->WithMetadataValue('TestGroup','$(_PayloadGroup)')->DistinctWithCase())" Condition="'$(_TestGroupingExists)' == 'true'" />
       <_PayloadFiles Include="$(_FileDirectory)*" Condition="'$(_TestGroupingExists)' == 'true'" />
+      <_PayloadFiles Include="$(_FileDirectory)/*.app" Condition="'$(_TestGroupingExists)' == 'true'" />
+      <_PayloadFiles Include="$(_FileDirectory)/*.app/**" Condition="'$(_TestGroupingExists)' == 'true'" />
 
       <_PayloadFiles Update="@(_PayloadFiles)">
         <!-- Never use [MSBuild]::MakeRelative here! We have some files containing Unicode characters in their %(FullPath) and
     <TimeoutPerTestInMilliseconds Condition=" '$(TimeoutPerTestInMinutes)' != '' ">$([System.TimeSpan]::FromMinutes($(TimeoutPerTestInMinutes)).TotalMilliseconds)</TimeoutPerTestInMilliseconds>
     <WaitForWorkItemCompletion>true</WaitForWorkItemCompletion>
     <_XUnitParallelMode>collections</_XUnitParallelMode>
-    <_XUnitParallelMode Condition=" '$(TargetOS)' == 'Android' ">none</_XUnitParallelMode>
+    <_XUnitParallelMode Condition=" '$(TargetOS)' == 'Android' or '$(TargetOS)' == 'iOSSimulator'">none</_XUnitParallelMode>
     <_XUnitParallelMode Condition=" '$(LongRunningGCTests)' == 'true' ">none</_XUnitParallelMode>
     <_XUnitParallelMode Condition=" '$(GcSimulatorTests)' == 'true' ">none</_XUnitParallelMode>
     <XUnitRunnerArgs>-parallel $(_XUnitParallelMode) -nocolor -noshadow -xml testResults.xml</XUnitRunnerArgs>
index 639d08a..fa4e20e 100644 (file)
       <AllTestAssemblies Include="$(BaseOutputPathWithConfig)\**\*.XUnitWrapper.dll" />
       <TestAssemblies Include="@(AllTestAssemblies)" Exclude="@(_SkipTestAssemblies -> '$(TestAssemblyDir)%(Identity)\%(Identity).XUnitWrapper.dll')" />
     </ItemGroup>
-    
+
     <Error  Text=" The wrappers must be compiled and placed at $(TestAssemblyDir)\*\ before they can be run, Do a clean Test Run"
             Condition="'@(AllTestAssemblies)' == ''" />
-    
+
     <Message Text= "AllTestAssemblies= @(AllTestAssemblies)"/>
     <Message Text= "TestAssemblies= @(TestAssemblies)"/>
     <Message Text= "_SkipTestAssemblies= @(_SkipTestAssemblies -> '$(TestAssemblyDir)%(Identity)\%(Identity).XUnitWrapper.dll')"/>
@@ -43,8 +43,8 @@
     <PropertyGroup>
       <XunitConsoleRunner>$(CORE_ROOT)\xunit\xunit.console.dll</XunitConsoleRunner>
 
-      <XunitArgs Condition="'$(TargetOS)' == 'Android'">-parallel none</XunitArgs>
-      <XunitArgs Condition="'$(TargetOS)' != 'Android'">-parallel $(ParallelRun)</XunitArgs>
+      <XunitArgs Condition="'$(TargetOS)' == 'Android' or '$(TargetOS)' == 'iOSSimulator'">-parallel none</XunitArgs>
+      <XunitArgs Condition="'$(TargetOS)' != 'Android' and '$(TargetOS)' != 'iOSSimulator'">-parallel $(ParallelRun)</XunitArgs>
       <XunitArgs>$(XunitArgs) -html $(__TestRunHtmlLog)</XunitArgs>
       <XunitArgs>$(XunitArgs) -xml $(__TestRunXmlLog)</XunitArgs>
       <XunitArgs>$(XunitArgs) @(IncludeTraitsItems->'-trait %(Identity)', ' ')</XunitArgs>
@@ -55,7 +55,7 @@
 
       <CorerunExecutable Condition="'$(RunningOnUnix)' == 'true'"  >$(CORE_ROOT)\corerun</CorerunExecutable>
       <CorerunExecutable Condition="'$(RunningOnUnix)' != 'true'">$(CORE_ROOT)\corerun.exe</CorerunExecutable>
-      <CorerunExecutable Condition="'$(TargetOS)' == 'Browser' Or '$(TargetOS)' == 'Android'"> $(DotnetRoot)/dotnet</CorerunExecutable>
+      <CorerunExecutable Condition="'$(TargetOS)' == 'Browser' Or '$(TargetOS)' == 'Android' or '$(TargetOS)' == 'iOSSimulator'"> $(DotnetRoot)/dotnet</CorerunExecutable>
     </PropertyGroup>
 
     <!-- Work around cmd command length limit by using relative paths
index 6a361c6..4bf4c58 100644 (file)
 
      <!-- There are currently no native project binaries on wasm or Android -->
      <Error  Text="The native project files are missing in $(NativeProjectOutputFolder) please run build from the root of the repo at least once"
-             Condition="'@(NativeProjectBinaries)' == '' And '$(TargetOS)' != 'Browser' And '$(TargetOS)' != 'Android'"/>
+             Condition="'@(NativeProjectBinaries)' == '' And '$(TargetOS)' != 'Browser' And '$(TargetOS)' != 'Android' And '$(TargetOS)' != 'iOS' And '$(TargetOS)' != 'iOSSimulator'"/>
 
      <Copy
         SourceFiles="@(NativeProjectBinaries)"
index 3d721d1..fec186b 100644 (file)
@@ -2,7 +2,7 @@
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <ReferenceSystemPrivateCoreLib>true</ReferenceSystemPrivateCoreLib>
-    <CLRTestTargetUnsupported Condition="'$(TargetOS)' == 'Browser' Or '$(TargetOS)' == 'Android'">true</CLRTestTargetUnsupported>
+    <CLRTestTargetUnsupported Condition="'$(TargetOS)' == 'Browser' Or '$(TargetOS)' == 'Android' Or '$(TargetOS)' == 'iOS' Or '$(TargetOS)' == 'iOSSimulator'">true</CLRTestTargetUnsupported>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="Castable.cs" />
index 7bd0ca0..ada9625 100755 (executable)
@@ -51,6 +51,13 @@ build_mono_aot()
     build_MSBuild_projects "Tests_MonoAot" "$__RepoRootDir/src/tests/run.proj" "Mono AOT compile tests" "/t:MonoAotCompileTests" "/p:RuntimeFlavor=$__RuntimeFlavor" "/p:MonoBinDir=$__MonoBinDir"
 }
 
+build_ios_apps()
+{
+    __RuntimeFlavor="mono" \
+    __Exclude="$__RepoRootDir/src/tests/issues.targets" \
+    build_MSBuild_projects "Create_iOS_App" "$__RepoRootDir/src/tests/run.proj" "Create iOS Apps" "/t:BuildAlliOSApp"
+}
+
 generate_layout()
 {
     echo "${__MsgPrefix}Creating test overlay..."
@@ -256,7 +263,7 @@ build_Tests()
         fi
     fi
 
-    if [[ "$__SkipNative" != 1 && "$__TargetOS" != "Browser" && "$__TargetOS" != "Android" ]]; then
+    if [[ "$__SkipNative" != 1 && "$__TargetOS" != "Browser" && "$__TargetOS" != "Android" && "$__TargetOS" != "iOS" && "$__TargetOS" != "iOSSimulator" ]]; then
         build_native "$__TargetOS" "$__BuildArch" "$__TestDir" "$__NativeTestIntermediatesDir" "install" "CoreCLR test component"
 
         if [[ "$?" -ne 0 ]]; then
@@ -624,6 +631,8 @@ echo "${__MsgPrefix}Test binaries are available at ${__TestBinDir}"
 
 if [ "$__TargetOS" == "Android" ]; then
     build_MSBuild_projects "Create_Android_App" "$__RepoRootDir/src/tests/run.proj" "Create Android Apps" "/t:BuildAllAndroidApp" "/p:RunWithAndroid=true"
+elif [ "$__TargetOS" == "iOS" ] || [ "$__TargetOS" == "iOSSimulator" ]; then
+    build_ios_apps
 fi
 
 if [[ "$__RunTests" -ne 0 ]]; then
index 747903c..d121409 100644 (file)
             <Issue>https://github.com/dotnet/runtime/issues/52781</Issue>
         </ExcludeList>
     </ItemGroup>
+
+    <ItemGroup Condition=" $(TargetOS) == 'iOSSimulator' " >
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Miscellaneous/MultipleAssembliesWithSamePInvoke/MAWSPITest/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Primitives/RuntimeHandles/RuntimeHandlesTest/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Primitives/Pointer/NonBlittablePointer/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Vector2_3_4/Vector2_3_4/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Primitives/Int/PInvokeIntTest/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Miscellaneous/HandleRef/HandleRefTest/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/ICustomMarshaler/ConflictingNames/SameNameDifferentAssembly/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Attributes/SuppressGCTransition/SuppressGCTransitionTest/**">
+            <Issue>missing assembly</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/NativeLibrary/API/NativeLibraryTests/**">
+            <Issue>System.PlatformNotSupportedException: Operation is not supported on this platform</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/NativeLibrary/Callback/CallbackStressTest_TargetUnix/**">
+            <Issue>System.DllNotFoundException: DoesNotExist</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/NativeLibrary/Callback/CallbackTests/**">
+            <Issue>System.DllNotFoundException: DoesNotExist</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/UnmanagedCallersOnly/UnmanagedCallersOnlyTest/**">
+            <Issue>System.DllNotFoundException: UnmanagedCallersOnlyDll</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/Interop/ObjectiveC/AutoReleaseTest/**">
+            <Issue>System.DllNotFoundException: ObjectiveC</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Interop/SuppressGCTransition/SuppressGCTransitionTest/**">
+            <Issue>System.DllNotFoundException: SuppressGCTransitionNative</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Regression/JitBlue/GitHub_36614/GitHub_36614/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly 'xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Methodical/gc_poll/InsertGCPoll/**">
+            <Issue>System.DllNotFoundException: GCPollNative</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/complus_config/name_config_with_pid/**">
+            <Issue>System.ArgumentNullException: Value cannot be null. (Parameter 'path1')</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/tracing/eventactivityidcontrol/eventactivityidcontrol/**">
+            <Issue>System.Exception: Values for 'retCode' are not equal! Left='1' Right='0'</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/readytorun/multifolder/multifolder/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly '/.../Library/Developer/CoreSimulator/Devices/941235AB-7563-4D79-AC28-946B7AD2304A/data/Containers/Bundle/Application/40176A30-D8F5-4497-958A-6514E5C684FC/readytorun_multifolder.app/testdir-multifolder/../FolderA/FolderA/FolderA.dll' or one of its dependencies.</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/Exceptions/ForeignThread/ForeignThreadExceptions/**">
+            <Issue>Failed to catch an exception! System.DllNotFoundException: ForeignThreadExceptionsNative</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/GC/API/WeakReference/multipleWRs/**">
+            <Issue>USAGE: MultipleWR.exe num objects [track]</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/GC/API/WeakReference/multipleWRs_1/**">
+            <Issue>USAGE: MultipleWR.exe num objects [track]</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/GC/API/GC/Collect_Optimized_2/**">
+            <Issue>GC_API 0|1|2</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/GC/API/GC/Collect_Optimized_3/**">
+            <Issue>GC_API 0|1|2</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/ilasm/PortablePdb/IlasmPortablePdbTests/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly 'xunit.runner.utility.netcoreapp10, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c' or one of its dependencies.</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/ilasm/System/Runtime/CompilerServices/MethodImplOptionsTests/**">
+            <Issue>Environment variable is not set: 'CORE_ROOT'</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/baseservices/threading/paramthreadstart/ThreadStartBool/**">
+            <Issue>USAGE: ThreadStartBool bool</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/baseservices/threading/paramthreadstart/ThreadStartBool_1/**">
+            <Issue>USAGE: ThreadStartBool bool</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/GC/LargeMemory/API/gc/gettotalmemory/**">
+            <Issue>System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/ThisCall/ThisCallTest/*">
+            <Issue>System.DllNotFoundException: ThisCallNative</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest/*">
+            <Issue>https://github.com/dotnet/runtime/issues/50440</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest/*">
+            <Issue>https://github.com/dotnet/runtime/issues/50440</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest/*">
+            <Issue>https://github.com/dotnet/runtime/issues/50440</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/Span/SpanBench/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/Serialization/Serialize/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/BenchmarksGame/fasta/fasta-1/**">
+            <Issue>((null) error) * Assertion at runtime/src/mono/mono/metadata/assembly.c:2049, condition `is_ok (error)' not met, function:mono_assembly_load_friends, Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/BenchmarksGame/regex-redux/regex-redux-5/**">
+            <Issue>((null) error) * Assertion at runtime/src/mono/mono/metadata/assembly.c:2049, condition `is_ok (error)' not met, function:mono_assembly_load_friends, Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/SIMD/RayTracer/RayTracer/**">
+            <Issue>((null) error) * Assertion at runtime/src/mono/mono/metadata/assembly.c:2049, condition `is_ok (error)' not met, function:mono_assembly_load_friends, Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/V8/Crypto/Crypto/**">
+            <Issue>((null) error) * Assertion at runtime/src/mono/mono/metadata/assembly.c:2049, condition `is_ok (error)' not met, function:mono_assembly_load_friends, Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/Roslyn/CscBench/**">
+            <Issue>This test requires CORE_ROOT to be set</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/BenchmarksGame/fannkuch-redux/fannkuch-redux-5/**">
+            <Issue>((null) error) * Assertion at runtime/src/mono/mono/metadata/assembly.c:2049, condition `is_ok (error)' not met, function:mono_assembly_load_friends, Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/SIMD/SeekUnroll/SeekUnroll/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly 'xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/BenchmarksGame/reverse-complement/reverse-complement-6/**">
+            <Issue>((null) error) * Assertion at runtime/src/mono/mono/metadata/assembly.c:2049, condition `is_ok (error)' not met, function:mono_assembly_load_friends, Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/BenchmarksGame/k-nucleotide/k-nucleotide-9/**">
+            <Issue>((null) error) * Assertion at runtime/src/mono/mono/metadata/assembly.c:2049, condition `is_ok (error)' not met, function:mono_assembly_load_friends, Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/Serialization/Deserialize/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Performance/CodeQuality/Span/Indexer/**">
+            <Issue>System.IO.FileNotFoundException: Could not load file or assembly 'xunit.performance.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67066efe964d3b03' or one of its dependencies.</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/CheckProjects/CheckProjects/*">
+            <Issue>CORE_ROOT must be set</Issue>
+        </ExcludeList>
+
+        <ExcludeList Include = "$(XunitTestBinBase)/JIT/Regression/JitBlue/GitHub_25468/GitHub_25468/**">
+            <Issue>Could not load file or assembly 'System.Drawing.Common, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies.</Issue>
+        </ExcludeList>
+    </ItemGroup>
 </Project>
index 4f17417..f21a0bd 100644 (file)
@@ -707,6 +707,112 @@ namespace $([System.String]::Copy($(Category)).Replace(".","_").Replace("\","").
     <MSBuild Projects="$(MSBuildProjectFile)" Targets="BuildAndroidApp" Properties="_CMDDIR=%(TestDirectories.Identity)" />
   </Target>
 
+  <UsingTask TaskName="AppleAppBuilderTask" AssemblyFile="$(AppleAppBuilderTasksAssemblyPath)" />
+  <UsingTask TaskName="MonoAOTCompiler" AssemblyFile="$(MonoAOTCompilerTasksAssemblyPath)" />
+
+  <Target Name="BuildiOSApp">
+    <PropertyGroup>
+      <CMDDIR_Grandparent>$([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetDirectoryName($(_CMDDIR)))))</CMDDIR_Grandparent>
+      <CategoryWithSlash>$([System.String]::Copy('$(_CMDDIR)').Replace("$(CMDDIR_Grandparent)/",""))</CategoryWithSlash>
+      <Category>$([System.String]::Copy('$(CategoryWithSlash)').Replace('/','_'))</Category>
+      <XUnitWrapperFileName>$([System.String]::Copy('$(CategoryWithSlash)').Replace('/', '.')).XUnitWrapper.dll</XUnitWrapperFileName>
+      <XUnitWrapperDll>$(CMDDIR_GrandParent)/$(CategoryWithSlash)/$(XUnitWrapperFileName)</XUnitWrapperDll>
+      <BuildDir>$(IntermediateOutputPath)\iOSApps\$(Category)</BuildDir>
+      <FinalPath>$(XUnitTestBinBase)$(CategoryWithSlash)\$(Category).app</FinalPath>
+    </PropertyGroup>
+
+    <PropertyGroup>
+      <AssemblyName>$(Category)</AssemblyName>
+      <MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.iossimulator-$(TargetArchitecture)/$(Configuration)/runtimes/iossimulator-$(TargetArchitecture)</MicrosoftNetCoreAppRuntimePackDir>
+      <MicrosoftNetCoreAppRuntimePackNativeDir>$(MicrosoftNetCoreAppRuntimePackDir)/native</MicrosoftNetCoreAppRuntimePackNativeDir>
+    </PropertyGroup>
+    <ItemGroup>
+      <AllTestScripts Include="$(_CMDDIR)\**\*.sh" />
+    </ItemGroup>
+    <ItemGroup>
+      <TestExclusions Include="@(ExcludeList->Metadata('FullPath'))" Condition="$(HaveExcludes)" />
+      <TestScripts Include="@(AllTestScripts)" Exclude="@(TestExclusions)" />
+      <TestDllPaths Include="$([System.IO.Path]::ChangeExtension('%(TestScripts.Identity)', 'dll'))" />
+      <TestDlls Include="%(TestDllPaths.Identity)" Condition="Exists(%(TestDllPaths.Identity))" />
+      <AssembliesInTestDirs Include="%(AllCMDsPresent.RelativeDir)*.dll" Exclude="@(TestAssemblies)"/>
+      <RuntimePackLibs Include="$(MicrosoftNetCoreAppRuntimePackDir)lib/**/*.dll" />
+    </ItemGroup>
+    <ItemGroup>
+      <ExtraDlls Include="%(TestDlls.RelativeDir)*.dll" Exclude="@(TestDlls)">
+        <TestDllFilename>@(TestDlls->'%(Filename)')</TestDllFilename>
+      </ExtraDlls>
+    </ItemGroup>
+    <PropertyGroup>
+      <BundleDir>$([MSBuild]::NormalizeDirectory('$(BuildDir)', 'AppBundle'))</BundleDir>
+    </PropertyGroup>
+    <ItemGroup>
+      <RuntimePackNativeLibs Include="$(MicrosoftNetCoreAppRuntimePackDir)/**/*.dll;$(MicrosoftNetCoreAppRuntimePackDir)/native/**/*.a;$(MicrosoftNetCoreAppRuntimePackDir)/native/**/*.dylib" />
+    </ItemGroup>
+
+    <RemoveDir Directories="$(BundleDir)" />
+    <RemoveDir Directories="$(BuildDir)" />
+
+    <MakeDir Directories="$(BuildDir)" />
+    <MakeDir Directories="$(BuildDir)/testdir-%(TestDlls.Filename)" />
+
+    <Copy
+        SourceFiles="@(RuntimePackNativeLibs)"
+        DestinationFolder="$(BuildDir)" />
+    <Copy
+        SourceFiles="@(RuntimePackLibs)"
+        DestinationFolder="$(BuildDir)" />
+    <Copy
+        SourceFiles="%(TestDlls.Identity)"
+        DestinationFolder="$(BuildDir)/testdir-%(TestDlls.Filename)" />
+    <Copy
+        SourceFiles="%(ExtraDlls.Identity)"
+        DestinationFolder="$(BuildDir)/testdir-%(ExtraDlls.TestDllFilename)" />
+
+    <AppleAppBuilderTask
+        TargetOS="$(TargetOS)"
+        Arch="$(TargetArchitecture)"
+        ProjectName="$(AssemblyName)"
+        MonoRuntimeHeaders="$(MicrosoftNetCoreAppRuntimePackNativeDir)/include/mono-2.0"
+        Assemblies="@(BundleAssemblies)"
+        ForceInterpreter="$(MonoForceInterpreter)"
+        UseConsoleUITemplate="True"
+        GenerateXcodeProject="True"
+        BuildAppBundle="True"
+        Optimized="True"
+        DevTeamProvisioning="$(DevTeamProvisioning)"
+        OutputDirectory="$(BundleDir)"
+        AppDir="$(BuildDir)"
+        InvariantGlobalization="true"
+    >
+      <Output TaskParameter="AppBundlePath" PropertyName="AppBundlePath" />
+      <Output TaskParameter="XcodeProjectPath" PropertyName="XcodeProjectPath" />
+    </AppleAppBuilderTask>
+
+    <!-- Apparently MSBuild cannot move directories and recursively copying a
+         a directory requires writing some sort of recursive traversal
+         logic yourself. -->
+    <ItemGroup>
+      <RecursiveCopyHack Include="$(AppBundlePath)/**/*.*" />
+    </ItemGroup>
+    <MakeDir Directories="$(FinalPath)" />
+    <Copy SourceFiles="@(RecursiveCopyHack)" DestinationFolder="$(FinalPath)/%(RecursiveDir)" />
+    <RemoveDir Directories="$(AppBundlePath)" />
+    <Message Importance="High" Text="App: $(FinalPath)" />
+  </Target>
+
+  <Target Name="BuildAlliOSApp" DependsOnTargets="GetListOfTestCmds;FindCmdDirectories">
+    <ItemGroup>
+      <RunProj Include="$(MSBuildProjectFile)">
+        <Properties>_CMDDIR=%(TestDirectories.Identity)</Properties>
+      </RunProj>
+    </ItemGroup>
+    <MSBuild
+      Projects="@(RunProj)"
+      Targets="BuildiOSApp"
+      BuildInParallel="true"
+      />
+  </Target>
+
   <Target Name="GetListOfTestCmds">
     <ItemGroup>
       <AllRunnableTestPaths Include="$(XunitTestBinBase)\**\*.$(TestScriptExtension)"/>