diff --git a/tests/xharness/BCLTestImporter/Templates/Managed/XamariniOSTemplate.cs b/tests/xharness/BCLTestImporter/Templates/Managed/XamariniOSTemplate.cs index 7131eb2d631f..8f95a27d5f4d 100644 --- a/tests/xharness/BCLTestImporter/Templates/Managed/XamariniOSTemplate.cs +++ b/tests/xharness/BCLTestImporter/Templates/Managed/XamariniOSTemplate.cs @@ -91,6 +91,9 @@ public class XamariniOSTemplate : ITemplatedProject { string WatchAppTemplatePath => Path.Combine (OutputDirectoryPath, "templates", "watchOS", "App").Replace ("/", "\\"); string WatchExtensionTemplatePath => Path.Combine (OutputDirectoryPath, "templates", "watchOS", "Extension").Replace ("/", "\\"); + bool srcGenerated = false; + object srcGeneratedLock = new object (); + Stream GetTemplateStream (string templateName) { var resources = GetType ().Assembly.GetManifestResourceNames (); @@ -229,24 +232,39 @@ internal static string GetPListPath (string rootDir, WatchAppType appType) } } - public async Task GenerateSource (string srcOuputPath) + // this method could be async, since we could be using async IO. The problem is that it might be called + // several times. The File and the Directory classes are not smart, and there is a possibility that we + // stop the thread between the Directory.Exist and the Directory.Delete. A nice way to solve this would be + // to set the src to get a AsyncLazy and generate the source only once on the first run, but AsyncLazy + // got move to target .netcore 5.0 (https://github.com/dotnet/runtime/issues/27510) yet we want to fix issue + // https://github.com/xamarin/xamarin-macios/issues/8240 so for the time being we lock, check, and generate + // if needed, which is better than: + // + // * Have a thread issue. + // * Generate the src EVERY SINGLE TIME for a list of projects. + public void GenerateSource (string srcOuputPath) { - // mk the expected directories - if (Directory.Exists (srcOuputPath)) - Directory.Delete (srcOuputPath, true); // delete, we always want to add the embeded src - BuildSrcTree (srcOuputPath); - // the code is simple, we are going to look for all the resources that we know are src and will write a - // copy of the stream in the designated output path - var resources = GetType ().Assembly.GetManifestResourceNames ().Where (a => a.StartsWith (srcResourcePrefix)); - - // we need to be smart, since the resource name != the path - foreach (var r in resources) { - var path = CalculateDestinationPath (srcOuputPath, r); - // copy the stream - using (var sourceReader = GetType ().Assembly.GetManifestResourceStream (r)) - using (var destination = File.Create (path)) { - await sourceReader.CopyToAsync (destination); + lock (srcGeneratedLock) { + if (srcGenerated) + return; + // mk the expected directories + if (Directory.Exists (srcOuputPath)) + Directory.Delete (srcOuputPath, true); // delete, we always want to add the embedded src + BuildSrcTree (srcOuputPath); + // the code is simple, we are going to look for all the resources that we know are src and will write a + // copy of the stream in the designated output path + var resources = GetType ().Assembly.GetManifestResourceNames ().Where (a => a.StartsWith (srcResourcePrefix)); + + // we need to be smart, since the resource name != the path + foreach (var r in resources) { + var path = CalculateDestinationPath (srcOuputPath, r); + // copy the stream + using (var sourceReader = GetType ().Assembly.GetManifestResourceStream (r)) + using (var destination = File.Create (path)) { + sourceReader.CopyTo (destination); + } } + srcGenerated = true; } } @@ -314,7 +332,7 @@ string GenerateIncludeFilesNode (string projectName, (string FailureMessage, Lis public async Task GenerateTestProjectsAsync (IEnumerable<(string Name, string [] Assemblies, string ExtraArgs, double TimeoutMultiplier)> projects, Platform platform) { // generate the template c# code before we create the diff projects - await GenerateSource (Path.Combine (OutputDirectoryPath, "templates")); + GenerateSource (Path.Combine (OutputDirectoryPath, "templates")); var result = new GeneratedProjects (); switch (platform) { case Platform.WatchOS: