From 4c53f68d8dbafb193675b6d3b0c05a81c7a0af94 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 11 Jul 2022 23:27:52 +0200 Subject: [PATCH] [siminstaller] Adjust according to recent Xcode changes. (#15449) The url to download the list of simulators that are available has changed, and the format changed slightly as well. --- tools/siminstaller/Program.cs | 91 +++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/tools/siminstaller/Program.cs b/tools/siminstaller/Program.cs index c790e76f79..ca7fc37259 100644 --- a/tools/siminstaller/Program.cs +++ b/tools/siminstaller/Program.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.IO; @@ -83,7 +84,7 @@ namespace xsiminstaller { var only_check = false; var force = false; var printHelp = false; - + var os = new OptionSet { { "xcode=", "The Xcode.app to use", (v) => xcode_app = v }, { "install=", "ID of simulator to install. Can be repeated multiple times.", (v) => install.Add (v) }, @@ -134,22 +135,49 @@ namespace xsiminstaller { xcodeVersion = xcodeVersion.Insert (xcodeVersion.Length - 2, "."); xcodeVersion = xcodeVersion.Insert (xcodeVersion.Length - 1, "."); - var url = $"https://devimages-cdn.apple.com/downloads/xcode/simulators/index-{xcodeVersion}-{xcodeUuid}.dvtdownloadableindex"; - var uri = new Uri (url); - var tmpfile = Path.Combine (TempDirectory, Path.GetFileName (uri.LocalPath)); + + var indexName = $"index-{xcodeVersion}-{xcodeUuid}.dvtdownloadableindex"; + var tmpfile = Path.Combine (TempDirectory, indexName); if (!File.Exists (tmpfile)) { - var wc = new WebClient (); - try { - if (verbose > 0) - Console.WriteLine ($"Downloading '{uri}'"); - wc.DownloadFile (uri, tmpfile); - } catch (Exception ex) { - // 403 means 404 - if (ex is WebException we && (we.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.Forbidden) { - Console.WriteLine ($"Failed to download {url}: Not found"); // Apple's servers return a 403 if the file doesn't exist, which can be quite confusing, so show a better error. - } else { - Console.WriteLine ($"Failed to download {url}: {ex}"); + // Try multiple urls + var urls = new string [] { + $"https://devimages-cdn.apple.com/downloads/xcode/simulators/{indexName}", + /* + * The following url was found while debugging Xcode, the "index2" part is actually hardcoded: + * + * DVTFoundation`-[DVTDownloadableIndexSource identifier]: + * 0x103db478d <+0>: pushq %rbp + * 0x103db478e <+1>: movq %rsp, %rbp + * 0x103db4791 <+4>: leaq 0x53f008(%rip), %rax ; @"index2" + * 0x103db4798 <+11>: popq %rbp + * 0x103db4799 <+12>: retq + * + */ + "https://devimages-cdn.apple.com/downloads/xcode/simulators/index2.dvtdownloadableindex", + }; + var anyFailures = false; + foreach (var url in urls) { + var uri = new Uri (url); + var wc = new WebClient (); + try { + if (verbose > 0) + Console.WriteLine ($"Downloading '{uri}'"); + else if (anyFailures) + Console.WriteLine ($"Attempting fallback url '{uri}'"); + wc.DownloadFile (uri, tmpfile); + } catch (Exception ex) { + File.Delete (tmpfile); // Make sure there are no downloaded remnants + // 403 means 404 + if (ex is WebException we && (we.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.Forbidden) { + Console.WriteLine ($"Failed to download {url}: Not found"); // Apple's servers return a 403 if the file doesn't exist, which can be quite confusing, so show a better error. + } else { + Console.WriteLine ($"Failed to download {url}: {ex}"); + } + anyFailures = true; } + } + + if (!File.Exists (tmpfile)) { // We couldn't download the list of simulators, but the simulator(s) we were requested to install might already be installed. // Don't fail in that case (we'd miss any potential updates, but that's probably not too bad). if (install.Count > 0) { @@ -169,13 +197,14 @@ namespace xsiminstaller { return 1; } } - if (!TryExecuteAndCapture ("plutil", $"-convert xml1 -o - '{tmpfile}'", out var xml)) + + if (!TryExecuteAndCapture ("plutil", $"-convert xml1 -o - '{tmpfile}'", out var xml)) return 1; var doc = new XmlDocument (); doc.LoadXml (xml); - var downloadables = doc.SelectNodes ("//plist/dict/key[text()='downloadables']/following-sibling::array/dict"); + var downloadables = doc.SelectNodes ("//plist/dict/key[text()='downloadables']/following-sibling::array[1]/dict"); foreach (XmlNode downloadable in downloadables) { var nameNode = downloadable.SelectSingleNode ("key[text()='name']/following-sibling::string"); var versionNode = downloadable.SelectSingleNode ("key[text()='version']/following-sibling::string"); @@ -200,7 +229,14 @@ namespace xsiminstaller { var name = Replace (nameNode.InnerText, dict); var source = Replace (sourceNode.InnerText, dict); - var installPrefix = Replace (installPrefixNode.InnerText, dict); + var installPrefix = Replace (installPrefixNode?.InnerText, dict); + + if (installPrefix is null) { + // This is just guesswork + var simRuntimeName = name.Replace (" Simulator", ".simruntime"); + installPrefix = $"/Library/Developer/CoreSimulator/Profiles/Runtimes/{simRuntimeName}"; + } + double.TryParse (fileSizeNode?.InnerText, out var parsedFileSize); var fileSize = (long) parsedFileSize; @@ -405,11 +441,28 @@ namespace xsiminstaller { return true; } - static string Replace (string value, Dictionary replacements) + [return: NotNullIfNotNull ("value")] + static string? Replace ( string? value, Dictionary replacements) { + if (value is null) + return null; foreach (var kvp in replacements) value = value.Replace ($"$({kvp.Key})", kvp.Value); return value; } } } + +#if !NET // the below attributes are no longer needed once we switch to .NET +namespace System.Diagnostics.CodeAnalysis { + [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + internal sealed class NotNullIfNotNullAttribute : Attribute { + public string ParameterName { get; } + + public NotNullIfNotNullAttribute (string parameterName) + { + ParameterName = parameterName; + } + } +} +#endif // !NET