diff --git a/msbuild/Xamarin.iOS.Tasks.Core/Tasks/ValidateAppBundleTaskBase.cs b/msbuild/Xamarin.iOS.Tasks.Core/Tasks/ValidateAppBundleTaskBase.cs index 2e7bad7bc547..7f0e4062c0e0 100644 --- a/msbuild/Xamarin.iOS.Tasks.Core/Tasks/ValidateAppBundleTaskBase.cs +++ b/msbuild/Xamarin.iOS.Tasks.Core/Tasks/ValidateAppBundleTaskBase.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -30,6 +31,324 @@ public PlatformFramework Framework { get { return PlatformFrameworkHelper.GetFramework (TargetFrameworkIdentifier); } } + void ValidateAppExtension (string path, string mainBundleIdentifier, string mainShortVersionString, string mainVersion) + { + var name = Path.GetFileNameWithoutExtension (path); + var info = Path.Combine (path, "Info.plist"); + if (!File.Exists (info)) { + Log.LogError ("The App Extension '{0}' does not contain an Info.plist", name); + return; + } + + var plist = PDictionary.FromFile (info); + + var bundleIdentifier = plist.GetCFBundleIdentifier (); + if (string.IsNullOrEmpty (bundleIdentifier)) { + Log.LogError ("The App Extension '{0}' does not specify a CFBundleIdentifier.", name); + return; + } + + // 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. + name = plist.GetCFBundleDisplayName () ?? name; + + var executable = plist.GetCFBundleExecutable (); + if (string.IsNullOrEmpty (executable)) + Log.LogError ("The App Extension '{0}' does not specify a CFBundleExecutable", name); + + if (!bundleIdentifier.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}).", name, bundleIdentifier, mainBundleIdentifier); + + if (bundleIdentifier.EndsWith (".key", StringComparison.Ordinal)) + Log.LogError ("The App Extension '{0}' has a CFBundleIdentifier ({1}) that ends with the illegal suffix \".key\".", name, bundleIdentifier); + + var shortVersionString = plist.GetCFBundleShortVersionString (); + if (string.IsNullOrEmpty (shortVersionString)) + Log.LogWarning ("The App Extension '{0}' does not specify a CFBundleShortVersionString", name); + + if (shortVersionString != mainShortVersionString) + Log.LogWarning ("The App Extension '{0}' has a CFBundleShortVersionString ({1}) that does not match the main app bundle's CFBundleShortVersionString ({2})", name, shortVersionString, mainShortVersionString); + + var version = plist.GetCFBundleVersion (); + if (string.IsNullOrEmpty (version)) + Log.LogWarning ("The App Extension '{0}' does not specify a CFBundleVersion", name); + + if (version != mainVersion) + Log.LogWarning ("The App Extension '{0}' has a CFBundleVersion ({1}) that does not match the main app bundle's CFBundleVersion ({2})", name, version, mainVersion); + + var extension = plist.Get ("NSExtension"); + if (extension == null) { + Log.LogError ("The App Extension '{0}' has an invalid Info.plist: it does not contain an NSExtension dictionary.", name); + return; + } + + var extensionPointIdentifier = extension.GetString ("NSExtensionPointIdentifier").Value; + + if (string.IsNullOrEmpty (extensionPointIdentifier)) { + Log.LogError ("The App Extension '{0}' has an invalid Info.plist: the NSExtension dictionary does not contain an NSExtensionPointIdentifier value.", name); + return; + } + + // https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/SystemExtensionKeys.html#//apple_ref/doc/uid/TP40014212-SW9 + switch (extensionPointIdentifier) { + 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 + var attributes = extension.Get ("NSExtensionAttributes"); + + if (attributes == null) { + Log.LogError ("The WatchKit Extension '{0}' has an invalid Info.plist: The NSExtension dictionary does not contain an NSExtensionAttributes dictionary.", name); + return; + } + + var wkAppBundleIdentifier = attributes.GetString ("WKAppBundleIdentifier").Value; + var apps = Directory.GetDirectories (path, "*.app"); + if (apps.Length == 0) { + Log.LogError ("The WatchKit Extension '{0}' does not contain any watch apps.", name); + } else if (apps.Length > 1) { + Log.LogError ("The WatchKit Extension '{0}' contain more than one watch apps.", name); + } else { + PObject requiredDeviceCapabilities; + + if (plist.TryGetValue ("UIRequiredDeviceCapabilities", out requiredDeviceCapabilities)) { + var requiredDeviceCapabilitiesDictionary = requiredDeviceCapabilities as PDictionary; + var requiredDeviceCapabilitiesArray = requiredDeviceCapabilities as PArray; + + if (requiredDeviceCapabilitiesDictionary != null) { + PBoolean watchCompanion; + + if (!requiredDeviceCapabilitiesDictionary.TryGetValue ("watch-companion", out watchCompanion) || !watchCompanion.Value) + Log.LogError ("The WatchKit Extension '{0}' has an invalid Info.plist: the UIRequiredDeviceCapabilities dictionary must contain the 'watch-companion' capability with a value of 'true'.", name); + } else if (requiredDeviceCapabilitiesArray != null) { + if (!requiredDeviceCapabilitiesArray.OfType ().Any (x => x.Value == "watch-companion")) + Log.LogError ("The WatchKit Extension '{0}' has an invalid Info.plist: the UIRequiredDeviceCapabilities array must contain the 'watch-companion' capability.", name); + } else { + Log.LogError ("The WatchKit Extension '{0}' has an invalid Info.plist: the UIRequiredDeviceCapabilities key must be present and contain the 'watch-companion' capability.", name); + } + } else { + Log.LogError ("The WatchKit Extension '{0}' has an invalid Info.plist: the UIRequiredDeviceCapabilities key must be present and contain the 'watch-companion' capability.", name); + } + + ValidateWatchOS1App (apps[0], name, mainBundleIdentifier, wkAppBundleIdentifier); + } + break; + default: + Log.LogWarning ("The App Extension '{0}' has an unrecognized NSExtensionPointIdentifier value ('{1}').", name, extensionPointIdentifier); + break; + } + } + + void ValidateWatchApp (string path, string mainBundleIdentifier, string mainShortVersionString, string mainVersion) + { + var name = Path.GetFileNameWithoutExtension (path); + var info = Path.Combine (path, "Info.plist"); + if (!File.Exists (info)) { + Log.LogError ("The Watch App '{0}' does not contain an Info.plist", name); + return; + } + + var plist = PDictionary.FromFile (info); + var bundleIdentifier = plist.GetCFBundleIdentifier (); + if (string.IsNullOrEmpty (bundleIdentifier)) { + Log.LogError ("The Watch App '{0}' does not specify a CFBundleIdentifier.", name); + return; + } + + if (!bundleIdentifier.StartsWith (mainBundleIdentifier + ".", StringComparison.Ordinal)) + Log.LogError ("The Watch App '{0}' has an invalid CFBundleIdentifier ({1}), it does not begin with the main app bundle's CFBundleIdentifier ({2}).", name, bundleIdentifier, mainBundleIdentifier); + + var shortVersionString = plist.GetCFBundleShortVersionString (); + if (string.IsNullOrEmpty (shortVersionString)) + Log.LogWarning ("The Watch App '{0}' does not specify a CFBundleShortVersionString", name); + + if (shortVersionString != mainShortVersionString) + Log.LogWarning ("The Watch App '{0}' has a CFBundleShortVersionString ({1}) that does not match the main app bundle's CFBundleShortVersionString ({2})", name, shortVersionString, mainShortVersionString); + + var version = plist.GetCFBundleVersion (); + if (string.IsNullOrEmpty (version)) + Log.LogWarning ("The Watch App '{0}' does not specify a CFBundleVersion", name); + + if (version != mainVersion) + Log.LogWarning ("The Watch App '{0}' has a CFBundleVersion ({1}) that does not match the main app bundle's CFBundleVersion ({2})", name, version, mainVersion); + + var watchDeviceFamily = plist.GetUIDeviceFamily (); + if (watchDeviceFamily != IPhoneDeviceType.Watch) + Log.LogError ("The Watch App '{0}' does not have a valid UIDeviceFamily value. Expected 'Watch (4)' but found '{1} ({2})'.", name, watchDeviceFamily.ToString (), (int) watchDeviceFamily); + + var watchExecutable = plist.GetCFBundleExecutable (); + if (string.IsNullOrEmpty (watchExecutable)) + Log.LogError ("The Watch App '{0}' does not specify a CFBundleExecutable", name); + + var wkCompanionAppBundleIdentifier = plist.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}').", name, wkCompanionAppBundleIdentifier, mainBundleIdentifier); + + PBoolean watchKitApp; + if (!plist.TryGetValue ("WKWatchKitApp", out watchKitApp) || !watchKitApp.Value) + Log.LogError ("The Watch App '{0}' has an invalid Info.plist: The WKWatchKitApp key must be present and have a value of 'true'.", name); + + if (plist.ContainsKey ("LSRequiresIPhoneOS")) + Log.LogError ("The Watch App '{0}' has an invalid Info.plist: the LSRequiresIPhoneOS key must not be present.", name); + + var pluginsDir = Path.Combine (path, "PlugIns"); + if (!Directory.Exists (pluginsDir)) { + Log.LogError ("The Watch App '{0}' does not contain any Watch Extensions.", name); + return; + } + + int count = 0; + foreach (var plugin in Directory.EnumerateDirectories (pluginsDir, "*.appex")) { + ValidateWatchExtension (plugin, bundleIdentifier, shortVersionString, version); + count++; + } + + if (count == 0) + Log.LogError ("The Watch App '{0}' does not contan a Watch Extension.", name); + } + + void ValidateWatchExtension (string path, string watchAppBundleIdentifier, string mainShortVersionString, string mainVersion) + { + var name = Path.GetFileNameWithoutExtension (path); + var info = Path.Combine (path, "Info.plist"); + if (!File.Exists (info)) { + Log.LogError ("The Watch Extension '{0}' does not contain an Info.plist", name); + return; + } + + var plist = PDictionary.FromFile (info); + + var bundleIdentifier = plist.GetCFBundleIdentifier (); + if (string.IsNullOrEmpty (bundleIdentifier)) { + Log.LogError ("The Watch Extension '{0}' does not specify a CFBundleIdentifier.", name); + return; + } + + // 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. + name = plist.GetCFBundleDisplayName () ?? name; + + var executable = plist.GetCFBundleExecutable (); + if (string.IsNullOrEmpty (executable)) + Log.LogError ("The Watch Extension '{0}' does not specify a CFBundleExecutable", name); + + if (!bundleIdentifier.StartsWith (watchAppBundleIdentifier + ".", StringComparison.Ordinal)) + Log.LogError ("The Watch Extension '{0}' has an invalid CFBundleIdentifier ({1}), it does not begin with the main app bundle's CFBundleIdentifier ({2}).", name, bundleIdentifier, watchAppBundleIdentifier); + + if (bundleIdentifier.EndsWith (".key", StringComparison.Ordinal)) + Log.LogError ("The Watch Extension '{0}' has a CFBundleIdentifier ({1}) that ends with the illegal suffix \".key\".", name, bundleIdentifier); + + var shortVersionString = plist.GetCFBundleShortVersionString (); + if (string.IsNullOrEmpty (shortVersionString)) + Log.LogWarning ("The Watch Extension '{0}' does not specify a CFBundleShortVersionString", name); + + if (shortVersionString != mainShortVersionString) + Log.LogWarning ("The Watch Extension '{0}' has a CFBundleShortVersionString ({1}) that does not match the main app bundle's CFBundleShortVersionString ({2})", name, shortVersionString, mainShortVersionString); + + var version = plist.GetCFBundleVersion (); + if (string.IsNullOrEmpty (version)) + Log.LogWarning ("The Watch Extension '{0}' does not specify a CFBundleVersion", name); + + if (version != mainVersion) + Log.LogWarning ("The Watch Extension '{0}' has a CFBundleVersion ({1}) that does not match the main app bundle's CFBundleVersion ({2})", name, version, mainVersion); + + var extension = plist.Get ("NSExtension"); + if (extension == null) { + Log.LogError ("The Watch Extension '{0}' has an invalid Info.plist: it does not contain an NSExtension dictionary.", name); + return; + } + + var extensionPointIdentifier = extension.Get ("NSExtensionPointIdentifier"); + if (extensionPointIdentifier != null) { + if (extensionPointIdentifier.Value != "com.apple.watchkit") + Log.LogError ("The Watch Extension '{0}' has an invalid Info.plist: the NSExtensionPointIdentifier must be \"com.apple.watchkit\".", name); + } else { + Log.LogError ("The Watch Extension '{0}' has an invalid Info.plist: the NSExtension dictionary must contain an NSExtensionPointIdentifier.", name); + } + + PDictionary attributes; + if (!extension.TryGetValue ("NSExtensionAttributes", out attributes)) { + Log.LogError ("The Watch Extension '{0}' has an invalid Info.plist: the NSExtension dictionary must contain NSExtensionAttributes.", name); + return; + } + + var appBundleIdentifier = attributes.Get ("WKAppBundleIdentifier"); + if (appBundleIdentifier != null) { + if (appBundleIdentifier.Value != watchAppBundleIdentifier) + Log.LogError ("The Watch Extension '{0}' has an invalid WKAppBundleIdentifier value ('{1}'), it does not match the parent Watch App bundle's CFBundleIdentifier ('{2}').", name, appBundleIdentifier.Value, watchAppBundleIdentifier); + } else { + Log.LogError ("The Watch Extension '{0}' has an invalid Info.plist: the NSExtensionAttributes dictionary must contain a WKAppBundleIdentifier.", name); + } + } + + void ValidateWatchOS1App (string path, string extensionName, string mainBundleIdentifier, string wkAppBundleIdentifier) + { + var name = Path.GetFileNameWithoutExtension (path); + var info = Path.Combine (path, "Info.plist"); + if (!File.Exists (info)) { + Log.LogError ("The Watch App '{0}' does not contain an Info.plist", name); + return; + } + + var plist = PDictionary.FromFile (info); + var bundleIdentifier = plist.GetCFBundleIdentifier (); + if (string.IsNullOrEmpty (bundleIdentifier)) { + Log.LogError ("The Watch App '{0}' does not specify a CFBundleIdentifier.", name); + return; + } + + var deviceFamily = plist.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 (deviceFamily != expectedDeviceFamily) + Log.LogError ("The Watch App '{0}' does not have a valid UIDeviceFamily value. Expected '{1}' but found '{2} ({3})'.", name, expectedDeviceFamilyString, deviceFamily.ToString (), (int) deviceFamily); + + var executable = plist.GetCFBundleExecutable (); + if (string.IsNullOrEmpty (executable)) + Log.LogError ("The Watch App '{0}' does not specify a CFBundleExecutable", name); + + if (bundleIdentifier != 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, bundleIdentifier); + + var companionAppBundleIdentifier = plist.Get ("WKCompanionAppBundleIdentifier"); + if (companionAppBundleIdentifier != null) { + if (companionAppBundleIdentifier.Value != mainBundleIdentifier) + Log.LogError ("The Watch App '{0}' has an invalid WKCompanionAppBundleIdentifier value ('{1}'), it does not match the main app bundle's CFBundleIdentifier ('{2}').", name, companionAppBundleIdentifier.Value, mainBundleIdentifier); + } else { + Log.LogError ("The Watch App '{0}' has an invalid Info.plist: the WKCompanionAppBundleIdentifier must exist and must match the main app bundle's CFBundleIdentifier.", name); + } + + if (plist.ContainsKey ("LSRequiresIPhoneOS")) + Log.LogError ("The Watch App '{0}' has an invalid Info.plist: the LSRequiresIPhoneOS key must not be present.", name); + } + public override bool Execute () { Log.LogTaskName ("ValidateAppBundle"); @@ -43,30 +362,31 @@ public override bool Execute () return false; } - var mainInfo = PDictionary.FromFile (mainInfoPath); + var plist = PDictionary.FromFile (mainInfoPath); - var mainBundleIdentifier = mainInfo.GetCFBundleIdentifier (); - if (string.IsNullOrEmpty (mainBundleIdentifier)) { + var bundleIdentifier = plist.GetCFBundleIdentifier (); + if (string.IsNullOrEmpty (bundleIdentifier)) { Log.LogError ("{0} does not specify a CFBundleIdentifier.", mainInfoPath); return false; } - var mainExecutable = mainInfo.GetCFBundleExecutable (); - if (string.IsNullOrEmpty (mainExecutable)) + var executable = plist.GetCFBundleExecutable (); + if (string.IsNullOrEmpty (executable)) 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) { + 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 { - mainPlatform = (PString) mainBundleSupportedPlatforms [0]; + platform = (PString) supportedPlatforms[0]; } // Validate UIDeviceFamily - var deviceTypes = mainInfo.GetUIDeviceFamily (); + var deviceTypes = plist.GetUIDeviceFamily (); var deviceFamilies = deviceTypes.ToDeviceFamily (); AppleDeviceFamily[] validFamilies = null; + switch (Framework) { case PlatformFramework.iOS: validFamilies = new AppleDeviceFamily[] { @@ -85,172 +405,30 @@ public override bool Execute () 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); + Log.LogError ("{0} is invalid: the UIDeviceFamily key must contain a value for '{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 mainShortVersionString = plist.GetCFBundleShortVersionString (); + var mainVersion = plist.GetCFBundleVersion (); - var dictNSExtension = extensionInfo.Get ("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 ("NSExtensionAttributes"); - var nsExtensionPointIdentifier = dictNSExtension.GetString ("NSExtensionPointIdentifier").Value; + if (Directory.Exists (Path.Combine (AppBundlePath, "PlugIns"))) { + foreach (var plugin in Directory.GetDirectories (Path.Combine (AppBundlePath, "PlugIns"), "*.appex")) + ValidateAppExtension (plugin, bundleIdentifier, mainShortVersionString, mainVersion); + } - 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; - } - } - } - } + 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; diff --git a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.props b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.props index 83355622c2a0..22b9605f4d57 100644 --- a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.props +++ b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.props @@ -24,10 +24,6 @@ Copyright (C) 2015-2016 Xamarin. All rights reserved. <_XamarinWatchOSAppExtensionCommonPropsHasBeenImported>true - - <_IsWatchExtension>true - - diff --git a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.targets b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.targets index 28b06f7065d9..e1ea84e87c88 100644 --- a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.targets +++ b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.WatchOS.AppExtension.Common.targets @@ -17,6 +17,7 @@ Copyright (C) 2015-2016 Xamarin. All rights reserved. True + True NSUrlSessionHandler diff --git a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.AppExtension.Common.targets b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.AppExtension.Common.targets index cc71a05a79c1..73400d2a9a63 100644 --- a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.AppExtension.Common.targets +++ b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.AppExtension.Common.targets @@ -17,6 +17,7 @@ Copyright (C) 2014-2016 Xamarin. All rights reserved. True + False diff --git a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.props b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.props index 0d2f57e2c9a6..6cc1864a2d7a 100644 --- a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.props +++ b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.props @@ -55,6 +55,7 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved. $(IsMacEnabled) False + False False True True diff --git a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.targets b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.targets index 4c5bd666496b..e921db6986f8 100644 --- a/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.targets +++ b/msbuild/Xamarin.iOS.Tasks.Core/Xamarin.iOS.Common.targets @@ -593,10 +593,6 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved. - - <_IsWatchExtension Condition="'$(_IsWatchExtension)' == ''">false - - - <_IsWatchExtension Condition="'@(_ResolvedWatchAppReferences)' != ''">true + True diff --git a/msbuild/tests/Xamarin.iOS.Tasks.Tests/ProjectsTests/Extensions/WatchKit.cs b/msbuild/tests/Xamarin.iOS.Tasks.Tests/ProjectsTests/Extensions/WatchKit.cs index 8adebdbfa93a..dc06c4655f9c 100644 --- a/msbuild/tests/Xamarin.iOS.Tasks.Tests/ProjectsTests/Extensions/WatchKit.cs +++ b/msbuild/tests/Xamarin.iOS.Tasks.Tests/ProjectsTests/Extensions/WatchKit.cs @@ -38,7 +38,7 @@ public void InvalidBundleIdTest () Engine.GlobalProperties.SetProperty ("Platform", Platform); AppBundlePath = mtouchPaths ["app_bundlepath"]; RunTarget (proj, "Build", 2); - Assert.AreEqual ("The App Extension WatchExtension has an invalid CFBundleIdentifier (com.xamarin.MyWatchApp.WatchExtension), it does not begin with the main app bundle's CFBundleIdentifier (com.xamarin.MyWatchAppX).", Engine.Logger.ErrorEvents [0].Message, "#1"); + Assert.AreEqual ("The App Extension 'WatchExtension' has an invalid CFBundleIdentifier (com.xamarin.MyWatchApp.WatchExtension), it does not begin with the main app bundle's CFBundleIdentifier (com.xamarin.MyWatchAppX).", Engine.Logger.ErrorEvents [0].Message, "#1"); Assert.AreEqual ("The Watch App 'WatchApp' has an invalid WKCompanionAppBundleIdentifier value ('com.xamarin.MyWatchApp'), it does not match the main app bundle's CFBundleIdentifier ('com.xamarin.MyWatchAppX').", Engine.Logger.ErrorEvents [1].Message, "#2"); } }