public static IPhoneDeviceType ToDeviceType(this AppleDeviceFamily family)
        {
            switch (family)
            {
            case AppleDeviceFamily.IPhone:
                return(IPhoneDeviceType.IPhone);

            case AppleDeviceFamily.IPad:
                return(IPhoneDeviceType.IPad);

            case AppleDeviceFamily.TV:
                return(IPhoneDeviceType.TV);

            case AppleDeviceFamily.Watch:
                return(IPhoneDeviceType.Watch);

            default:
                throw new ArgumentOutOfRangeException(string.Format("Unknown device family: {0}", family));
            }
        }
示例#2
0
        public override bool Execute()
        {
            var mainInfoPath = PlatformFrameworkHelper.GetAppManifestPath(Platform, AppBundlePath);

            if (!File.Exists(mainInfoPath))
            {
                Log.LogError(7040, AppBundlePath, MSBStrings.E7040, AppBundlePath);
                return(false);
            }

            var plist = PDictionary.FromFile(mainInfoPath);

            var bundleIdentifier = plist.GetCFBundleIdentifier();

            if (string.IsNullOrEmpty(bundleIdentifier))
            {
                Log.LogError(7041, mainInfoPath, MSBStrings.E7041, mainInfoPath);
                return(false);
            }

            var executable = plist.GetCFBundleExecutable();

            if (string.IsNullOrEmpty(executable))
            {
                Log.LogError(7042, mainInfoPath, MSBStrings.E7042, mainInfoPath);
            }

            var supportedPlatforms = plist.GetArray(ManifestKeys.CFBundleSupportedPlatforms);
            var platform           = string.Empty;

            if (supportedPlatforms == null || supportedPlatforms.Count == 0)
            {
                Log.LogError(7043, mainInfoPath, MSBStrings.E7043, mainInfoPath);
            }
            else
            {
                platform = (PString)supportedPlatforms[0];
            }

            // Validate UIDeviceFamily
            var deviceTypes    = plist.GetUIDeviceFamily();
            var deviceFamilies = deviceTypes.ToDeviceFamily();

            AppleDeviceFamily[]  validFamilies    = null;
            AppleDeviceFamily [] requiredFamilies = null;

            switch (Platform)
            {
            case ApplePlatform.MacCatalyst:
                requiredFamilies = new AppleDeviceFamily [] {
                    AppleDeviceFamily.IPad,
                };
                goto case ApplePlatform.iOS;

            case ApplePlatform.iOS:
                validFamilies = new AppleDeviceFamily[] {
                    AppleDeviceFamily.IPhone,
                    AppleDeviceFamily.IPad,
                };
                break;

            case ApplePlatform.WatchOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.Watch };
                break;

            case ApplePlatform.TVOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.TV };
                break;

            default:
                Log.LogError("Invalid platform: {0}", Platform);
                break;
            }

            if (validFamilies != null)
            {
                if (validFamilies.Length == 0)
                {
                    Log.LogError(7044, mainInfoPath, MSBStrings.E7044, mainInfoPath);
                }
                else
                {
                    foreach (var family in deviceFamilies)
                    {
                        if (Array.IndexOf(validFamilies, family) == -1)
                        {
                            Log.LogError(7044, mainInfoPath, MSBStrings.E7044_A, mainInfoPath, family);
                        }
                    }
                }
            }

            if (requiredFamilies != null)
            {
                foreach (var family in requiredFamilies)
                {
                    if (!deviceFamilies.Contains(family))
                    {
                        Log.LogError(7044, mainInfoPath, MSBStrings.E7044_A, mainInfoPath, family);
                    }
                }
            }

            var mainShortVersionString = plist.GetCFBundleShortVersionString();
            var mainVersion            = plist.GetCFBundleVersion();

            if (Directory.Exists(Path.Combine(AppBundlePath, "PlugIns")))
            {
                foreach (var plugin in Directory.GetDirectories(Path.Combine(AppBundlePath, "PlugIns"), "*.appex"))
                {
                    ValidateAppExtension(plugin, bundleIdentifier, mainShortVersionString, mainVersion);
                }
            }

            if (Directory.Exists(Path.Combine(AppBundlePath, "Watch")))
            {
                foreach (var watchApp in Directory.GetDirectories(Path.Combine(AppBundlePath, "Watch"), "*.app"))
                {
                    ValidateWatchApp(watchApp, bundleIdentifier, mainShortVersionString, mainVersion);
                }
            }

            return(!Log.HasLoggedErrors);
        }
示例#3
0
        public override bool Execute()
        {
            Log.LogTaskName("ValidateAppBundle");
            Log.LogTaskProperty("AppBundlePath", Path.GetFullPath(AppBundlePath));
            Log.LogTaskProperty("SdkIsSimulator", SdkIsSimulator);
            Log.LogTaskProperty("TargetFrameworkIdentifier", TargetFrameworkIdentifier);

            var mainInfoPath = Path.Combine(AppBundlePath, "Info.plist");

            if (!File.Exists(mainInfoPath))
            {
                Log.LogError("The app bundle {0} does not contain an Info.plist.", AppBundlePath);
                return(false);
            }

            var plist = PDictionary.FromFile(mainInfoPath);

            var bundleIdentifier = plist.GetCFBundleIdentifier();

            if (string.IsNullOrEmpty(bundleIdentifier))
            {
                Log.LogError("{0} does not specify a CFBundleIdentifier.", mainInfoPath);
                return(false);
            }

            var executable = plist.GetCFBundleExecutable();

            if (string.IsNullOrEmpty(executable))
            {
                Log.LogError("{0} does not specify a CFBundleExecutable", mainInfoPath);
            }

            var supportedPlatforms = plist.GetArray(ManifestKeys.CFBundleSupportedPlatforms);
            var platform           = string.Empty;

            if (supportedPlatforms == null || supportedPlatforms.Count == 0)
            {
                Log.LogError("{0} does not specify a CFBundleSupportedPlatforms.", mainInfoPath);
            }
            else
            {
                platform = (PString)supportedPlatforms[0];
            }

            // Validate UIDeviceFamily
            var deviceTypes    = plist.GetUIDeviceFamily();
            var deviceFamilies = deviceTypes.ToDeviceFamily();

            AppleDeviceFamily[] validFamilies = null;

            switch (Framework)
            {
            case PlatformFramework.iOS:
                validFamilies = new AppleDeviceFamily[] {
                    AppleDeviceFamily.IPhone,
                    AppleDeviceFamily.IPad,
                    AppleDeviceFamily.Watch
                };
                break;

            case PlatformFramework.WatchOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.Watch };
                break;

            case PlatformFramework.TVOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.TV };
                break;

            default:
                Log.LogError("Invalid framework: {0}", Framework);
                break;
            }

            if (validFamilies != null)
            {
                if (validFamilies.Length == 0)
                {
                    Log.LogError("{0} does not specify a UIDeviceFamily.", mainInfoPath);
                }
                else
                {
                    foreach (var family in deviceFamilies)
                    {
                        if (Array.IndexOf(validFamilies, family) == -1)
                        {
                            Log.LogError("{0} is invalid: the UIDeviceFamily key must contain a value for '{1}'.", mainInfoPath, family);
                        }
                    }
                }
            }

            var mainShortVersionString = plist.GetCFBundleShortVersionString();
            var mainVersion            = plist.GetCFBundleVersion();

            if (Directory.Exists(Path.Combine(AppBundlePath, "PlugIns")))
            {
                foreach (var plugin in Directory.GetDirectories(Path.Combine(AppBundlePath, "PlugIns"), "*.appex"))
                {
                    ValidateAppExtension(plugin, bundleIdentifier, mainShortVersionString, mainVersion);
                }
            }

            if (Directory.Exists(Path.Combine(AppBundlePath, "Watch")))
            {
                foreach (var watchApp in Directory.GetDirectories(Path.Combine(AppBundlePath, "Watch"), "*.app"))
                {
                    ValidateWatchApp(watchApp, bundleIdentifier, mainShortVersionString, mainVersion);
                }
            }

            return(!Log.HasLoggedErrors);
        }
示例#4
0
        public override bool Execute()
        {
            Log.LogTaskName("ValidateAppBundle");
            Log.LogTaskProperty("AppBundlePath", Path.GetFullPath(AppBundlePath));
            Log.LogTaskProperty("SdkIsSimulator", SdkIsSimulator);
            Log.LogTaskProperty("TargetFrameworkIdentifier", TargetFrameworkIdentifier);

            var mainInfoPath = Path.Combine(AppBundlePath, "Info.plist");

            if (!File.Exists(mainInfoPath))
            {
                Log.LogError("The app bundle {0} does not contain an Info.plist.", AppBundlePath);
                return(false);
            }

            var mainInfo = PDictionary.FromFile(mainInfoPath);

            var mainBundleIdentifier = mainInfo.GetCFBundleIdentifier();

            if (string.IsNullOrEmpty(mainBundleIdentifier))
            {
                Log.LogError("{0} does not specify a CFBundleIdentifier.", mainInfoPath);
                return(false);
            }

            var mainExecutable = mainInfo.GetCFBundleExecutable();

            if (string.IsNullOrEmpty(mainExecutable))
            {
                Log.LogError("{0} does not specify a CFBundleExecutable", mainInfoPath);
            }

            var mainBundleSupportedPlatforms = mainInfo.GetArray(ManifestKeys.CFBundleSupportedPlatforms);
            var mainPlatform = string.Empty;

            if (mainBundleSupportedPlatforms == null || mainBundleSupportedPlatforms.Count == 0)
            {
                Log.LogError("{0} does not specify a CFBundleSupportedPlatforms.", mainInfoPath);
            }
            else
            {
                mainPlatform = (PString)mainBundleSupportedPlatforms [0];
            }

            // Validate UIDeviceFamily
            var deviceTypes    = mainInfo.GetUIDeviceFamily();
            var deviceFamilies = deviceTypes.ToDeviceFamily();

            AppleDeviceFamily[] validFamilies = null;
            switch (Framework)
            {
            case PlatformFramework.iOS:
                validFamilies = new AppleDeviceFamily[] {
                    AppleDeviceFamily.IPhone,
                    AppleDeviceFamily.IPad,
                    AppleDeviceFamily.Watch
                };
                break;

            case PlatformFramework.WatchOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.Watch };
                break;

            case PlatformFramework.TVOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.TV };
                break;

            default:
                Log.LogError("Invalid framework: {0}", Framework);
                break;
            }
            if (validFamilies != null)
            {
                if (validFamilies.Length == 0)
                {
                    Log.LogError("{0} does not specify a UIDeviceFamily.", mainInfoPath);
                }
                else
                {
                    foreach (var family in deviceFamilies)
                    {
                        if (Array.IndexOf(validFamilies, family) == -1)
                        {
                            Log.LogError("{0} specifies an invalid UIDeviceFamily: {1}.", mainInfoPath, family);
                        }
                    }
                }
            }

            if (Directory.Exists(Path.Combine(AppBundlePath, "PlugIns")))
            {
                var mainShortVersionString = mainInfo.GetCFBundleShortVersionString();
                var mainVersion            = mainInfo.GetCFBundleVersion();

                foreach (var plugin in Directory.GetDirectories(Path.Combine(AppBundlePath, "PlugIns")))
                {
                    var extensionName     = Path.GetFileNameWithoutExtension(plugin);
                    var extensionInfoPath = Path.Combine(plugin, "Info.plist");
                    if (!File.Exists(extensionInfoPath))
                    {
                        Log.LogError("The App Extension {0} does not contain an Info.plist", extensionName);
                        continue;
                    }

                    var extensionInfo = PDictionary.FromFile(extensionInfoPath);

                    var extensionBundleIdentifier = extensionInfo.GetCFBundleIdentifier();
                    if (string.IsNullOrEmpty(extensionBundleIdentifier))
                    {
                        Log.LogError("The App Extension {0} does not specify a CFBundleIdentifier.", extensionName);
                        continue;
                    }
                    // The filename of the extension path is the extension's bundle identifier, which turns out ugly
                    // in error messages. Try to get something more friendly-looking.
                    extensionName = extensionInfo.GetCFBundleDisplayName() ?? extensionName;

                    var extensionExecutable = extensionInfo.GetCFBundleExecutable();
                    if (string.IsNullOrEmpty(extensionExecutable))
                    {
                        Log.LogError("The App Extension {0} does not specify a CFBundleExecutable", extensionName);
                    }

                    if (!extensionBundleIdentifier.StartsWith(mainBundleIdentifier + ".", StringComparison.Ordinal))
                    {
                        Log.LogError("The App Extension {0} has an invalid CFBundleIdentifier ({1}), it does not begin with the main app bundle's CFBundleIdentifier ({2}).", extensionName, extensionBundleIdentifier, mainBundleIdentifier);
                    }

                    if (extensionBundleIdentifier.EndsWith(".key", StringComparison.Ordinal))
                    {
                        Log.LogError("The App Extension {0} has a CFBundleIdentifier ({1}) that ends with the illegal suffix \".key\".", extensionName, extensionBundleIdentifier);
                    }

                    var extensionShortVersionString = extensionInfo.GetCFBundleShortVersionString();
                    if (string.IsNullOrEmpty(extensionShortVersionString))
                    {
                        Log.LogWarning("The App Extension {0} does not specify a CFBundleShortVersionString", extensionName);
                    }

                    if (extensionShortVersionString != mainShortVersionString)
                    {
                        Log.LogWarning("The App Extension {0} has a CFBundleShortVersionString ({1}) that does not match the main app bundle's CFBundleShortVersionString ({2})", extensionName, extensionShortVersionString, mainShortVersionString);
                    }

                    var extensionVersion = extensionInfo.GetCFBundleVersion();
                    if (string.IsNullOrEmpty(extensionVersion))
                    {
                        Log.LogWarning("The App Extension {0} does not specify a CFBundleVersion", extensionName);
                    }

                    if (extensionVersion != mainVersion)
                    {
                        Log.LogWarning("The App Extension {0} has a CFBundleVersion ({1}) that does not match the main app bundle's CFBundleVersion ({2})", extensionName, extensionVersion, mainVersion);
                    }

                    var dictNSExtension = extensionInfo.Get <PDictionary> ("NSExtension");
                    if (dictNSExtension == null)
                    {
                        Log.LogError("The App Extension '{0}' has an invalid Info.plist: it does not contain an NSExtension dictionary.", extensionName);
                    }
                    else
                    {
                        var nsExtensionAttributes      = dictNSExtension.Get <PDictionary> ("NSExtensionAttributes");
                        var nsExtensionPointIdentifier = dictNSExtension.GetString("NSExtensionPointIdentifier").Value;

                        if (string.IsNullOrEmpty(nsExtensionPointIdentifier))
                        {
                            Log.LogError("The App Extension '{0}' has an invalid Info.plist: the NSExtension dictionary does not contain an NSExtensionPointIdentifier value.", extensionName);
                        }
                        else
                        {
                            // https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/SystemExtensionKeys.html#//apple_ref/doc/uid/TP40014212-SW9
                            switch (nsExtensionPointIdentifier)
                            {
                            case "com.apple.ui-services":                          // iOS+OSX
                            case "com.apple.services":                             // iOS
                            case "com.apple.keyboard-service":                     // iOS
                            case "com.apple.fileprovider-ui":                      // iOS
                            case "com.apple.fileprovider-nonui":                   // iOS
                            case "com.apple.FinderSync":                           // OSX
                            case "com.apple.photo-editing":                        // iOS
                            case "com.apple.share-services":                       // iOS+OSX
                            case "com.apple.widget-extension":                     // iOS+OSX
                            case "com.apple.Safari.content-blocker":               // iOS
                            case "com.apple.Safari.sharedlinks-service":           // iOS
                            case "com.apple.spotlight.index":                      // iOS
                            case "com.apple.AudioUnit-UI":                         // iOS
                            case "com.apple.tv-services":                          // tvOS
                            case "com.apple.broadcast-services":                   // iOS+tvOS
                            case "com.apple.callkit.call-directory":               // iOS
                            case "com.apple.message-payload-provider":             // iOS
                            case "com.apple.intents-service":                      // iOS
                            case "com.apple.intents-ui-service":                   // iOS
                            case "com.apple.usernotifications.content-extension":  // iOS
                            case "com.apple.usernotifications.service":            // iOS
                                break;

                            case "com.apple.watchkit":                             // iOS8.2
                                if (nsExtensionAttributes == null)
                                {
                                    Log.LogError("The WatchKit Extension '{0}' has an invalid Info.plist: The NSExtension dictionary does not contain an NSExtensionAttributes dictionary.", extensionName);
                                }
                                else
                                {
                                    var wkAppBundleIdentifier = nsExtensionAttributes.GetString("WKAppBundleIdentifier").Value;
                                    var apps = Directory.GetDirectories(plugin, "*.app");
                                    if (apps.Length == 0)
                                    {
                                        Log.LogError("The WatchKit Extension '{0}' does not contain any watch apps.", extensionName);
                                    }
                                    else if (apps.Length > 1)
                                    {
                                        Log.LogError("The WatchKit Extension '{0}' contain more than one watch apps.", extensionName);
                                    }
                                    else
                                    {
                                        var watchName     = Path.GetFileNameWithoutExtension(apps [0]);
                                        var watchInfoPath = Path.Combine(apps [0], "Info.plist");
                                        if (!File.Exists(watchInfoPath))
                                        {
                                            Log.LogError("The Watch App '{0}' does not contain an Info.plist", watchName);
                                            continue;
                                        }
                                        var watchInfo             = PDictionary.FromFile(watchInfoPath);
                                        var watchBundleIdentifier = watchInfo.GetCFBundleIdentifier();
                                        if (string.IsNullOrEmpty(watchBundleIdentifier))
                                        {
                                            Log.LogError("The Watch App '{0}' does not specify a CFBundleIdentifier.", watchName);
                                            continue;
                                        }

                                        var watchDeviceFamily = watchInfo.GetUIDeviceFamily();
                                        IPhoneDeviceType expectedDeviceFamily;
                                        string           expectedDeviceFamilyString;
                                        if (SdkIsSimulator)
                                        {
                                            expectedDeviceFamily       = IPhoneDeviceType.Watch | IPhoneDeviceType.IPhone;
                                            expectedDeviceFamilyString = "IPhone, Watch (1, 4)";
                                        }
                                        else
                                        {
                                            expectedDeviceFamily       = IPhoneDeviceType.Watch;
                                            expectedDeviceFamilyString = "Watch (4)";
                                        }
                                        if (watchDeviceFamily != expectedDeviceFamily)
                                        {
                                            Log.LogError("The Watch App '{0}' does not have a valid UIDeviceFamily value. Expected '{1}' found '{2} ({3})'.", watchName, expectedDeviceFamilyString, watchDeviceFamily.ToString(), (int)(watchDeviceFamily));
                                        }

                                        var watchExecutable = watchInfo.GetCFBundleExecutable();
                                        if (string.IsNullOrEmpty(watchExecutable))
                                        {
                                            Log.LogError("The Watch App '{0}' does not specify a CFBundleExecutable", watchName);
                                        }

                                        if (watchBundleIdentifier != wkAppBundleIdentifier)
                                        {
                                            Log.LogError("The WatchKit Extension '{0}' has an invalid WKAppBundleIdentifier value ('{1}'), it does not match the Watch App's CFBundleIdentifier ('{2}').", extensionName, wkAppBundleIdentifier, watchBundleIdentifier);
                                        }

                                        var wkCompanionAppBundleIdentifier = watchInfo.GetString("WKCompanionAppBundleIdentifier").Value;
                                        if (wkCompanionAppBundleIdentifier != mainBundleIdentifier)
                                        {
                                            Log.LogError("The Watch App '{0}' has an invalid WKCompanionAppBundleIdentifier value ('{1}'), it does not match the main app bundle's CFBundleIdentifier ('{2}').", watchName, wkCompanionAppBundleIdentifier, mainBundleIdentifier);
                                        }

                                        if (watchInfo.ContainsKey("LSRequiresIPhoneOS"))
                                        {
                                            Log.LogError("The Watch App '{0}' has an invalid Info.plist: the LSRequiresIPhoneOS key must not be present.");
                                        }

                                        var watchUIDeviceFamilies = watchInfo.GetArray("UIDeviceFamily");
                                        if (watchUIDeviceFamilies != null)
                                        {
                                            var found = false;
                                            foreach (PNumber number in watchUIDeviceFamilies)
                                            {
                                                if (number != null && number.Value == 4)
                                                {
                                                    found = true;
                                                    break;
                                                }
                                            }
                                            if (!found)
                                            {
                                                Log.LogError("The Watch App '{0}' has an invalid Info.plist: the list of supported UIDeviceFamily values must include '4'.");
                                            }
                                        }
                                    }
                                }
                                break;

                            default:
                                Log.LogWarning("The App Extension '{0}' has an unrecognized NSExtensionPointIdentifier value ('{1}').", extensionName, nsExtensionPointIdentifier);
                                break;
                            }
                        }
                    }
                }
            }

            return(!Log.HasLoggedErrors);
        }
示例#5
0
        public override bool Execute()
        {
            Log.LogTaskName ("ValidateAppBundle");
            Log.LogTaskProperty ("AppBundlePath", Path.GetFullPath (AppBundlePath));
            Log.LogTaskProperty ("SdkIsSimulator", SdkIsSimulator);
            Log.LogTaskProperty ("TargetFrameworkIdentifier", TargetFrameworkIdentifier);

            var mainInfoPath = Path.Combine (AppBundlePath, "Info.plist");
            if (!File.Exists (mainInfoPath)) {
                Log.LogError ("The app bundle {0} does not contain an Info.plist.", AppBundlePath);
                return false;
            }

            var mainInfo = PDictionary.FromFile (mainInfoPath);

            var mainBundleIdentifier = mainInfo.GetCFBundleIdentifier ();
            if (string.IsNullOrEmpty (mainBundleIdentifier)) {
                Log.LogError ("{0} does not specify a CFBundleIdentifier.", mainInfoPath);
                return false;
            }

            var mainExecutable = mainInfo.GetCFBundleExecutable ();
            if (string.IsNullOrEmpty (mainExecutable))
                Log.LogError ("{0} does not specify a CFBundleExecutable", mainInfoPath);

            var mainBundleSupportedPlatforms = mainInfo.GetArray (ManifestKeys.CFBundleSupportedPlatforms);
            var mainPlatform = string.Empty;
            if (mainBundleSupportedPlatforms == null || mainBundleSupportedPlatforms.Count == 0) {
                Log.LogError ("{0} does not specify a CFBundleSupportedPlatforms.", mainInfoPath);
            } else {
                mainPlatform = (PString) mainBundleSupportedPlatforms [0];
            }

            // Validate UIDeviceFamily
            var deviceTypes = mainInfo.GetUIDeviceFamily ();
            var deviceFamilies = deviceTypes.ToDeviceFamily ();
            AppleDeviceFamily[] validFamilies = null;
            switch (Framework) {
            case PlatformFramework.iOS:
                validFamilies = new AppleDeviceFamily[] {
                    AppleDeviceFamily.IPhone,
                    AppleDeviceFamily.IPad,
                    AppleDeviceFamily.Watch
                };
                break;
            case PlatformFramework.WatchOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.Watch };
                break;
            case PlatformFramework.TVOS:
                validFamilies = new AppleDeviceFamily[] { AppleDeviceFamily.TV };
                break;
            default:
                Log.LogError ("Invalid framework: {0}", Framework);
                break;
            }
            if (validFamilies != null) {
                if (validFamilies.Length == 0) {
                    Log.LogError ("{0} does not specify a UIDeviceFamily.", mainInfoPath);
                } else {
                    foreach (var family in deviceFamilies) {
                        if (Array.IndexOf (validFamilies, family) == -1) {
                            Log.LogError ("{0} specifies an invalid UIDeviceFamily: {1}.", mainInfoPath, family);
                        }
                    }
                }
            }

            if (Directory.Exists (Path.Combine (AppBundlePath, "PlugIns"))) {
                var mainShortVersionString = mainInfo.GetCFBundleShortVersionString ();
                var mainVersion = mainInfo.GetCFBundleVersion ();

                foreach (var plugin in Directory.GetDirectories (Path.Combine (AppBundlePath, "PlugIns"))) {
                    var extensionName = Path.GetFileNameWithoutExtension (plugin);
                    var extensionInfoPath = Path.Combine (plugin, "Info.plist");
                    if (!File.Exists (extensionInfoPath)) {
                        Log.LogError ("The App Extension {0} does not contain an Info.plist", extensionName);
                        continue;
                    }

                    var extensionInfo = PDictionary.FromFile (extensionInfoPath);

                    var extensionBundleIdentifier = extensionInfo.GetCFBundleIdentifier ();
                    if (string.IsNullOrEmpty (extensionBundleIdentifier)) {
                        Log.LogError ("The App Extension {0} does not specify a CFBundleIdentifier.", extensionName);
                        continue;
                    }
                    // The filename of the extension path is the extension's bundle identifier, which turns out ugly
                    // in error messages. Try to get something more friendly-looking.
                    extensionName = extensionInfo.GetCFBundleDisplayName () ?? extensionName;

                    var extensionExecutable = extensionInfo.GetCFBundleExecutable ();
                    if (string.IsNullOrEmpty (extensionExecutable))
                        Log.LogError ("The App Extension {0} does not specify a CFBundleExecutable", extensionName);

                    if (!extensionBundleIdentifier.StartsWith (mainBundleIdentifier + ".", StringComparison.Ordinal))
                        Log.LogError ("The App Extension {0} has an invalid CFBundleIdentifier ({1}), it does not begin with the main app bundle's CFBundleIdentifier ({2}).", extensionName, extensionBundleIdentifier, mainBundleIdentifier);

                    if (extensionBundleIdentifier.EndsWith (".key", StringComparison.Ordinal))
                        Log.LogError ("The App Extension {0} has a CFBundleIdentifier ({1}) that ends with the illegal suffix \".key\".", extensionName, extensionBundleIdentifier);

                    var extensionShortVersionString = extensionInfo.GetCFBundleShortVersionString ();
                    if (string.IsNullOrEmpty (extensionShortVersionString))
                        Log.LogWarning ("The App Extension {0} does not specify a CFBundleShortVersionString", extensionName);

                    if (extensionShortVersionString != mainShortVersionString)
                        Log.LogWarning("The App Extension {0} has a CFBundleShortVersionString ({1}) that does not match the main app bundle's CFBundleShortVersionString ({2})", extensionName, extensionShortVersionString, mainShortVersionString);

                    var extensionVersion = extensionInfo.GetCFBundleVersion ();
                    if (string.IsNullOrEmpty (extensionVersion))
                        Log.LogWarning ("The App Extension {0} does not specify a CFBundleVersion", extensionName);

                    if (extensionVersion != mainVersion)
                        Log.LogWarning ("The App Extension {0} has a CFBundleVersion ({1}) that does not match the main app bundle's CFBundleVersion ({2})", extensionName, extensionVersion, mainVersion);

                    var dictNSExtension = extensionInfo.Get<PDictionary> ("NSExtension");
                    if (dictNSExtension == null) {
                        Log.LogError ("The App Extension '{0}' has an invalid Info.plist: it does not contain an NSExtension dictionary.", extensionName);
                    } else {
                        var nsExtensionAttributes = dictNSExtension.Get<PDictionary> ("NSExtensionAttributes");
                        var nsExtensionPointIdentifier = dictNSExtension.GetString ("NSExtensionPointIdentifier").Value;

                        if (string.IsNullOrEmpty (nsExtensionPointIdentifier)) {
                            Log.LogError ("The App Extension '{0}' has an invalid Info.plist: the NSExtension dictionary does not contain an NSExtensionPointIdentifier value.", extensionName);
                        } else {
                            // https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/SystemExtensionKeys.html#//apple_ref/doc/uid/TP40014212-SW9
                            switch (nsExtensionPointIdentifier) {
                            case "com.apple.ui-services": // iOS+OSX
                            case "com.apple.services": // iOS
                            case "com.apple.keyboard-service": // iOS
                            case "com.apple.fileprovider-ui": // iOS
                            case "com.apple.fileprovider-nonui": // iOS
                            case "com.apple.FinderSync": // OSX
                            case "com.apple.photo-editing": // iOS
                            case "com.apple.share-services": // iOS+OSX
                            case "com.apple.widget-extension": // iOS+OSX
                            case "com.apple.Safari.content-blocker": // iOS
                            case "com.apple.Safari.sharedlinks-service": // iOS
                            case "com.apple.spotlight.index": // iOS
                            case "com.apple.tv-services": // tvOS
                                break;
                            case "com.apple.watchkit": // iOS8.2
                                if (nsExtensionAttributes == null) {
                                    Log.LogError ("The WatchKit Extension '{0}' has an invalid Info.plist: The NSExtension dictionary does not contain an NSExtensionAttributes dictionary.", extensionName);
                                } else {
                                    var wkAppBundleIdentifier = nsExtensionAttributes.GetString ("WKAppBundleIdentifier").Value;
                                    var apps = Directory.GetDirectories (plugin, "*.app");
                                    if (apps.Length == 0) {
                                        Log.LogError ("The WatchKit Extension '{0}' does not contain any watch apps.", extensionName);
                                    } else if (apps.Length > 1) {
                                        Log.LogError ("The WatchKit Extension '{0}' contain more than one watch apps.", extensionName);
                                    } else {
                                        var watchName = Path.GetFileNameWithoutExtension (apps [0]);
                                        var watchInfoPath = Path.Combine (apps [0], "Info.plist");
                                        if (!File.Exists (watchInfoPath)) {
                                            Log.LogError ("The Watch App '{0}' does not contain an Info.plist", watchName);
                                            continue;
                                        }
                                        var watchInfo = PDictionary.FromFile (watchInfoPath);
                                        var watchBundleIdentifier = watchInfo.GetCFBundleIdentifier ();
                                        if (string.IsNullOrEmpty (watchBundleIdentifier)) {
                                            Log.LogError ("The Watch App '{0}' does not specify a CFBundleIdentifier.", watchName);
                                            continue;
                                        }

                                        var watchDeviceFamily = watchInfo.GetUIDeviceFamily ();
                                        IPhoneDeviceType expectedDeviceFamily;
                                        string expectedDeviceFamilyString;
                                        if (SdkIsSimulator) {
                                            expectedDeviceFamily = IPhoneDeviceType.Watch | IPhoneDeviceType.IPhone;
                                            expectedDeviceFamilyString = "IPhone, Watch (1, 4)";
                                        } else {
                                            expectedDeviceFamily = IPhoneDeviceType.Watch;
                                            expectedDeviceFamilyString = "Watch (4)";
                                        }
                                        if (watchDeviceFamily != expectedDeviceFamily)
                                            Log.LogError ("The Watch App '{0}' does not have a valid UIDeviceFamily value. Expected '{1}' found '{2} ({3})'.", watchName, expectedDeviceFamilyString, watchDeviceFamily.ToString (), (int) (watchDeviceFamily));

                                        var watchExecutable = watchInfo.GetCFBundleExecutable ();
                                        if (string.IsNullOrEmpty (watchExecutable))
                                            Log.LogError ("The Watch App '{0}' does not specify a CFBundleExecutable", watchName);

                                        if (watchBundleIdentifier != wkAppBundleIdentifier)
                                            Log.LogError ("The WatchKit Extension '{0}' has an invalid WKAppBundleIdentifier value ('{1}'), it does not match the Watch App's CFBundleIdentifier ('{2}').", extensionName, wkAppBundleIdentifier, watchBundleIdentifier);

                                        var wkCompanionAppBundleIdentifier = watchInfo.GetString ("WKCompanionAppBundleIdentifier").Value;
                                        if (wkCompanionAppBundleIdentifier != mainBundleIdentifier)
                                            Log.LogError ("The Watch App '{0}' has an invalid WKCompanionAppBundleIdentifier value ('{1}'), it does not match the main app bundle's CFBundleIdentifier ('{2}').", watchName, wkCompanionAppBundleIdentifier, mainBundleIdentifier);

                                        if (watchInfo.ContainsKey ("LSRequiresIPhoneOS"))
                                            Log.LogError ("The Watch App '{0}' has an invalid Info.plist: the LSRequiresIPhoneOS key must not be present.");

                                        var watchUIDeviceFamilies = watchInfo.GetArray ("UIDeviceFamily");
                                        if (watchUIDeviceFamilies != null) {
                                            var found = false;
                                            foreach (PNumber number in watchUIDeviceFamilies) {
                                                if (number != null && number.Value == 4) {
                                                    found = true;
                                                    break;
                                                }
                                            }
                                            if (!found)
                                                Log.LogError ("The Watch App '{0}' has an invalid Info.plist: the list of supported UIDeviceFamily values must include '4'.");
                                        }
                                    }
                                }
                                break;
                            default:
                                Log.LogWarning ("The App Extension '{0}' has an unrecognized NSExtensionPointIdentifier value ('{1}').", extensionName, nsExtensionPointIdentifier);
                                break;
                            }
                        }
                    }
                }
            }

            return !Log.HasLoggedErrors;
        }