From ef8f1dcbfe49651490f9f37245cc682e3be690ff Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 5 Nov 2018 08:16:29 +0100 Subject: [PATCH] [mtouch/mmp] Unify Xcode lookup & verification code in mtouch and mmp. (#5077) Share all the code to find and verify Xcode between mtouch and mmp. Also print out the actual product version when logging which Xcode was used (to make it easier to detect if a beta version is in use). --- tools/common/Driver.cs | 103 +++++++++++++++++++++++++++++++++++++++-- tools/mmp/driver.cs | 99 ++------------------------------------- tools/mmp/mmp.csproj | 3 ++ tools/mtouch/mtouch.cs | 97 ++------------------------------------ 4 files changed, 112 insertions(+), 190 deletions(-) diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index ac1c35c420..8202846b01 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -16,6 +16,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Xamarin.MacDev; using Xamarin.Utils; using ObjCRuntime; @@ -23,6 +24,8 @@ namespace Xamarin.Bundler { public partial class Driver { static void AddSharedOptions (Application app, Mono.Options.OptionSet options) { + options.Add ("sdkroot=", "Specify the location of Apple SDKs, default to 'xcode-select' value.", v => sdk_root = v); + options.Add ("no-xcode-version-check", "Ignores the Xcode version check.", v => { min_xcode_version = null; }, true /* This is a non-documented option. Please discuss any customers running into the xcode version check on the maciosdev@ list before giving this option out to customers. */); options.Add ("warnaserror:", "An optional comma-separated list of warning codes that should be reported as errors (if no warnings are specified all warnings are reported as errors).", v => { try { @@ -386,10 +389,10 @@ namespace Xamarin.Bundler { return System.Reflection.Assembly.GetExecutingAssembly ().Location; } - static string xcode_bundle_version; - public static string XcodeBundleVersion { + static string xcode_product_version; + public static string XcodeProductVersion { get { - return xcode_bundle_version; + return xcode_product_version; } } @@ -500,5 +503,99 @@ namespace Xamarin.Bundler { Console.Write ("!"); Console.WriteLine ("Timestamp {0}: {1} ms", msg, watch.ElapsedMilliseconds); } + + internal static PDictionary FromPList (string name) + { + if (!File.Exists (name)) + throw ErrorHelper.CreateError (24, "Could not find required file '{0}'.", name); + return PDictionary.FromFile (name); + } + + const string XcodeDefault = "/Applications/Xcode.app"; + + static string FindSystemXcode () + { + var output = new StringBuilder (); + if (Driver.RunCommand ("xcode-select", "-p", output: output) != 0) { + ErrorHelper.Warning (59, "Could not find the currently selected Xcode on the system: {0}", output.ToString ()); + return null; + } + return output.ToString ().Trim (); + } + + static string sdk_root; + static string developer_directory; + + public static string DeveloperDirectory { + get { + return developer_directory; + } + } + + static void ValidateXcode (bool accept_any_xcode_version, bool warn_if_not_found) + { + if (sdk_root == null) { + sdk_root = FindSystemXcode (); + if (sdk_root == null) { + // FindSystemXcode showed a warning in this case. In particular do not use 'string.IsNullOrEmpty' here, + // because FindSystemXcode may return an empty string (with no warning printed) if the xcode-select command + // succeeds, but returns nothing. + sdk_root = null; + } else if (!Directory.Exists (sdk_root)) { + ErrorHelper.Warning (60, "Could not find the currently selected Xcode on the system. 'xcode-select --print-path' returned '{0}', but that directory does not exist.", sdk_root); + sdk_root = null; + } else { + if (!accept_any_xcode_version) + ErrorHelper.Warning (61, "No Xcode.app specified (using --sdkroot), using the system Xcode as reported by 'xcode-select --print-path': {0}", sdk_root); + } + if (sdk_root == null) { + sdk_root = XcodeDefault; + if (!Directory.Exists (sdk_root)) { + if (warn_if_not_found) { + // mmp: and now we give up, but don't throw like mtouch, because we don't want to change behavior (this sometimes worked it appears) + ErrorHelper.Warning (56, "Cannot find Xcode in any of our default locations. Please install Xcode, or pass a custom path using --sdkroot=."); + return; // Can't validate the version below if we can't even find Xcode... + } + + throw ErrorHelper.CreateError (56, "Cannot find Xcode in the default location (/Applications/Xcode.app). Please install Xcode, or pass a custom path using --sdkroot ."); + } + ErrorHelper.Warning (62, "No Xcode.app specified (using --sdkroot or 'xcode-select --print-path'), using the default Xcode instead: {0}", sdk_root); + } + } else if (!Directory.Exists (sdk_root)) { + throw ErrorHelper.CreateError (55, "The Xcode path '{0}' does not exist.", sdk_root); + } + + // Check what kind of path we got + if (File.Exists (Path.Combine (sdk_root, "Contents", "MacOS", "Xcode"))) { + // path to the Xcode.app + developer_directory = Path.Combine (sdk_root, "Contents", "Developer"); + } else if (File.Exists (Path.Combine (sdk_root, "..", "MacOS", "Xcode"))) { + // path to Contents/Developer + developer_directory = Path.GetFullPath (Path.Combine (sdk_root, "..", "..", "Contents", "Developer")); + } else { + throw ErrorHelper.CreateError (57, "Cannot determine the path to Xcode.app from the sdk root '{0}'. Please specify the full path to the Xcode.app bundle.", sdk_root); + } + + var plist_path = Path.Combine (Path.GetDirectoryName (DeveloperDirectory), "version.plist"); + + if (File.Exists (plist_path)) { + var plist = FromPList (plist_path); + var version = plist.GetString ("CFBundleShortVersionString"); + xcode_version = new Version (version); + xcode_product_version = plist.GetString ("ProductBuildVersion"); + } else { + throw ErrorHelper.CreateError (58, "The Xcode.app '{0}' is invalid (the file '{1}' does not exist).", Path.GetDirectoryName (Path.GetDirectoryName (DeveloperDirectory)), plist_path); + } + + if (!accept_any_xcode_version) { + if (min_xcode_version != null && XcodeVersion < min_xcode_version) + throw ErrorHelper.CreateError (51, "{3} {0} requires Xcode {4} or later. The current Xcode version (found in {2}) is {1}.", Constants.Version, XcodeVersion.ToString (), sdk_root, PRODUCT, min_xcode_version); + + if (XcodeVersion < SdkVersions.XcodeVersion) + ErrorHelper.Warning (79, "The recommended Xcode version for {3} {0} is Xcode {3} or later. The current Xcode version (found in {2}) is {1}.", Constants.Version, XcodeVersion.ToString (), sdk_root, SdkVersions.Xcode, PRODUCT); + } + + Driver.Log (1, "Using Xcode {0} ({2}) found in {1}", XcodeVersion, sdk_root, XcodeProductVersion); + } } } diff --git a/tools/mmp/driver.cs b/tools/mmp/driver.cs index 495e9fd1c3..5e28319598 100644 --- a/tools/mmp/driver.cs +++ b/tools/mmp/driver.cs @@ -68,6 +68,7 @@ namespace Xamarin.Bundler { public static partial class Driver { internal const string NAME = "mmp"; + const string PRODUCT = "Xamarin.Mac"; internal static Application App = new Application (Environment.GetCommandLineArgs ()); static Target BuildTarget = new Target (App); static List references = new List (); @@ -100,7 +101,6 @@ namespace Xamarin.Bundler { static string mmp_dir; static string mono_dir; - static string sdk_root; static string custom_bundle_name; static string tls_provider; @@ -120,6 +120,8 @@ namespace Xamarin.Bundler { const string pkg_config = "/Library/Frameworks/Mono.framework/Commands/pkg-config"; + static Version min_xcode_version = new Version (6, 0); + static void ShowHelp (OptionSet os) { Console.WriteLine ("mmp - Xamarin.Mac Packer"); Console.WriteLine ("Copyright 2010 Novell Inc."); @@ -270,7 +272,6 @@ namespace Xamarin.Bundler { { "i|icon=", "Use the specified file as the bundle icon", v => { icon = v; }}, { "xml=", "Provide an extra XML definition file to the linker", v => App.Definitions.Add (v) }, { "time", v => WatchLevel++ }, - { "sdkroot=", "Specify the location of Apple SDKs", v => sdk_root = v }, { "arch=", "Specify the architecture ('i386' or 'x86_64') of the native runtime (default to 'i386')", v => { arch = v; arch_set = true; } }, { "profile=", "(Obsoleted in favor of --target-framework) Specify the .NET profile to use (defaults to '" + Xamarin.Utils.TargetFramework.Default + "')", v => SetTargetFramework (v) }, { "target-framework=", "Specify the .NET target framework to use (defaults to '" + Xamarin.Utils.TargetFramework.Default + "')", v => SetTargetFramework (v) }, @@ -477,7 +478,7 @@ namespace Xamarin.Bundler { } } - ValidateXcode (); + ValidateXcode (false, true); App.Initialize (); @@ -584,47 +585,6 @@ namespace Xamarin.Bundler { } } - - static void ValidateXcode () - { - if (xcode_version == null) { - // Check what kind of path we got - if (File.Exists (Path.Combine (sdk_root, "Contents", "MacOS", "Xcode"))) { - // path to the Xcode.app - sdk_root = Path.Combine (sdk_root, "Contents", "Developer"); - } else if (File.Exists (Path.Combine (sdk_root, "..", "MacOS", "Xcode"))) { - // path to Contents/Developer - sdk_root = Path.GetFullPath (Path.Combine (sdk_root, "..", "..", "Contents", "Developer")); - } else { - throw ErrorHelper.CreateError (57, "Cannot determine the path to Xcode.app from the sdk root '{0}'. Please specify the full path to the Xcode.app bundle.", sdk_root); - } - - var plist_path = Path.Combine (Path.GetDirectoryName (DeveloperDirectory), "version.plist"); - if (File.Exists (plist_path)) { - bool nextElement = false; - XmlReaderSettings settings = new XmlReaderSettings (); - settings.DtdProcessing = DtdProcessing.Ignore; - using (XmlReader reader = XmlReader.Create (plist_path, settings)) { - while (reader.Read()) { - // We want the element after CFBundleShortVersionString - if (reader.NodeType == XmlNodeType.Element) { - if (reader.Name == "key") { - if (reader.ReadElementContentAsString() == "CFBundleShortVersionString") - nextElement = true; - } - if (nextElement && reader.Name == "string") { - nextElement = false; - xcode_version = new Version (reader.ReadElementContentAsString()); - } - } - } - } - } else { - throw ErrorHelper.CreateError (58, "The Xcode.app '{0}' is invalid (the file '{1}' does not exist).", Path.GetDirectoryName (Path.GetDirectoryName (DeveloperDirectory)), plist_path); - } - } - } - // SDK versions are only passed in as X.Y but some frameworks/APIs require X.Y.Z // Mutate them if we have a new enough Xcode static Version MutateSDKVersionToPointRelease (Version rv) @@ -897,57 +857,6 @@ namespace Xamarin.Bundler { Watch ("Extracted native link info", 1); } - static string FindSystemXcode () - { - var output = new StringBuilder (); - if (RunCommand ("xcode-select", "-p", output: output) != 0) { - ErrorHelper.Warning (59, "Could not find the currently selected Xcode on the system: {0}", output.ToString ()); - return null; - } - return output.ToString ().Trim (); - } - - static string DeveloperDirectory { - get { - if (sdk_root == null) - sdk_root = LocateXcode (); - return sdk_root; - } - } - - static string LocateXcode () - { - // DEVELOPER_DIR overrides `xcrun` so it should have priority - string user_developer_directory = Environment.GetEnvironmentVariable ("DEVELOPER_DIR"); - if (!String.IsNullOrEmpty (user_developer_directory)) - return user_developer_directory; - - // Next let's respect xcode-select -p if it exists - string systemXCodePath = FindSystemXcode (); - if (!String.IsNullOrEmpty (systemXCodePath)) { - if (!Directory.Exists (systemXCodePath)) { - ErrorHelper.Warning (60, "Could not find the currently selected Xcode on the system. 'xcode-select --print-path' returned '{0}', but that directory does not exist.", systemXCodePath); - } - else { - return systemXCodePath; - } - } - - // Now the fallback locaions we uses to use (for backwards compat) - const string Xcode43Default = "/Applications/Xcode.app/Contents/Developer"; - const string XcrunMavericks = "/Library/Developer/CommandLineTools"; - - if (Directory.Exists (Xcode43Default)) - return Xcode43Default; - - if (Directory.Exists (XcrunMavericks)) - return XcrunMavericks; - - // And now we give up, but don't throw like mtouch, because we don't want to change behavior (this sometimes worked it appears) - ErrorHelper.Warning (56, "Cannot find Xcode in any of our default locations. Please install Xcode, or pass a custom path using --sdkroot=."); - return string.Empty; - } - static string MonoDirectory { get { if (mono_dir == null) { diff --git a/tools/mmp/mmp.csproj b/tools/mmp/mmp.csproj index c364ca2102..165046195d 100644 --- a/tools/mmp/mmp.csproj +++ b/tools/mmp/mmp.csproj @@ -370,6 +370,9 @@ common\CoreResolver.cs + + external\PListObject.cs + diff --git a/tools/mtouch/mtouch.cs b/tools/mtouch/mtouch.cs index 12790a34db..a08ba28ab4 100644 --- a/tools/mtouch/mtouch.cs +++ b/tools/mtouch/mtouch.cs @@ -80,6 +80,7 @@ namespace Xamarin.Bundler { public partial class Driver { internal const string NAME = "mtouch"; + internal const string PRODUCT = "Xamarin.iOS"; public static void ShowHelp (OptionSet os) { @@ -117,7 +118,7 @@ namespace Xamarin.Bundler Embeddinator, } - static bool xcode_version_check = true; + static Version min_xcode_version = new Version (6, 0); // // Output generation @@ -315,87 +316,6 @@ namespace Xamarin.Bundler } } - static string sdk_root; - static string developer_directory; - - const string XcodeDefault = "/Applications/Xcode.app"; - - static string FindSystemXcode () - { - var output = new StringBuilder (); - if (Driver.RunCommand ("xcode-select", "-p", output: output) != 0) { - ErrorHelper.Warning (59, "Could not find the currently selected Xcode on the system: {0}", output.ToString ()); - return null; - } - return output.ToString ().Trim (); - } - - static void ValidateXcode (Action action) - { - // Allow a few actions, since these seem to always work no matter the Xcode version. - var accept_any_xcode_version = action == Action.ListDevices || action == Action.ListCrashReports || action == Action.ListApps || action == Action.LogDev; - - if (sdk_root == null) { - sdk_root = FindSystemXcode (); - if (sdk_root == null) { - // FindSystemXcode showed a warning in this case. In particular do not use 'string.IsNullOrEmpty' here, - // because FindSystemXcode may return an empty string (with no warning printed) if the xcode-select command - // succeeds, but returns nothing. - sdk_root = null; - } else if (!Directory.Exists (sdk_root)) { - ErrorHelper.Warning (60, "Could not find the currently selected Xcode on the system. 'xcode-select --print-path' returned '{0}', but that directory does not exist.", sdk_root); - sdk_root = null; - } else { - if (!accept_any_xcode_version) - ErrorHelper.Warning (61, "No Xcode.app specified (using --sdkroot), using the system Xcode as reported by 'xcode-select --print-path': {0}", sdk_root); - } - if (sdk_root == null) { - sdk_root = XcodeDefault; - if (!Directory.Exists (sdk_root)) - throw ErrorHelper.CreateError (56, "Cannot find Xcode in the default location (/Applications/Xcode.app). Please install Xcode, or pass a custom path using --sdkroot ."); - ErrorHelper.Warning (62, "No Xcode.app specified (using --sdkroot or 'xcode-select --print-path'), using the default Xcode instead: {0}", sdk_root); - } - } else if (!Directory.Exists (sdk_root)) { - throw ErrorHelper.CreateError (55, "The Xcode path '{0}' does not exist.", sdk_root); - } - - // Check what kind of path we got - if (File.Exists (Path.Combine (sdk_root, "Contents", "MacOS", "Xcode"))) { - // path to the Xcode.app - developer_directory = Path.Combine (sdk_root, "Contents", "Developer"); - } else if (File.Exists (Path.Combine (sdk_root, "..", "MacOS", "Xcode"))) { - // path to Contents/Developer - developer_directory = Path.GetFullPath (Path.Combine (sdk_root, "..", "..", "Contents", "Developer")); - } else { - throw ErrorHelper.CreateError (57, "Cannot determine the path to Xcode.app from the sdk root '{0}'. Please specify the full path to the Xcode.app bundle.", sdk_root); - } - - var plist_path = Path.Combine (Path.GetDirectoryName (DeveloperDirectory), "version.plist"); - - if (File.Exists (plist_path)) { - var plist = FromPList (plist_path); - var version = plist.GetString ("CFBundleShortVersionString"); - xcode_version = new Version (version); - xcode_bundle_version = plist.GetString ("CFBundleVersion"); - } else { - throw ErrorHelper.CreateError (58, "The Xcode.app '{0}' is invalid (the file '{1}' does not exist).", Path.GetDirectoryName (Path.GetDirectoryName (developer_directory)), plist_path); - } - - if (xcode_version_check && XcodeVersion < new Version (6, 0)) - ErrorHelper.Error (51, "Xamarin.iOS {0} requires Xcode 6.0 or later. The current Xcode version (found in {2}) is {1}.", Constants.Version, XcodeVersion.ToString (), sdk_root); - - if (XcodeVersion < new Version (7, 0) && !accept_any_xcode_version) - ErrorHelper.Warning (79, "The recommended Xcode version for Xamarin.iOS {0} is Xcode 7.0 or later. The current Xcode version (found in {2}) is {1}.", Constants.Version, XcodeVersion.ToString (), sdk_root); - - Driver.Log (1, "Using Xcode {0} found in {1}", XcodeVersion, sdk_root); - } - - public static string DeveloperDirectory { - get { - return developer_directory; - } - } - public static string GetFrameworkDirectory (Application app) { return GetFrameworkDir (GetPlatform (app), app.SdkVersion); @@ -895,13 +815,6 @@ namespace Xamarin.Bundler return true; } - internal static PDictionary FromPList (string name) - { - if (!File.Exists (name)) - throw new MonoTouchException (24, true, "Could not find required file '{0}'.", name); - return PDictionary.FromFile (name); - } - internal static bool TryParseBool (string value, out bool result) { if (string.IsNullOrEmpty (value)) { @@ -1201,9 +1114,7 @@ namespace Xamarin.Bundler }, { "stderr=", "Redirect the standard error for the simulated application to the specified file [DEPRECATED]", v => { }, true }, { "stdout=", "Redirect the standard output for the simulated application to the specified file [DEPRECATED]", v => { }, true }, - { "sdkroot=", "Specify the location of Apple SDKs, default to 'xcode-select' value.", v => sdk_root = v }, - { "no-xcode-version-check", "Ignores the Xcode version check.", v => { xcode_version_check = false; }, true /* This is a non-documented option. Please discuss any customers running into the xcode version check on the maciosdev@ list before giving this option out to customers. */ }, { "mono:", "Comma-separated list of options for how the Mono runtime should be included. Possible values: 'static' (link statically), 'framework' (linked as a user framework), '[no-]package-framework' (if the Mono.framework should be copied to the app bundle or not. The default value is 'framework' for extensions, and main apps if the app targets iOS 8.0 or later and contains extensions, otherwise 'static'. The Mono.framework will be copied to the app bundle if mtouch detects it's needed, but this may be overridden if the default values for 'framework' vs 'static' is overwridden.", v => { foreach (var opt in v.Split (new char [] { ',' })) { @@ -1327,7 +1238,9 @@ namespace Xamarin.Bundler ErrorHelper.Verbosity = verbose; - ValidateXcode (action); + // Allow a few actions, since these seem to always work no matter the Xcode version. + var accept_any_xcode_version = action == Action.ListDevices || action == Action.ListCrashReports || action == Action.ListApps || action == Action.LogDev; + ValidateXcode (accept_any_xcode_version, false); switch (action) { /* Device actions */