예제 #1
0
        void AddIconPaths(PArray icons, PArray iconFiles, string productsDir)
        {
            foreach (var icon in iconFiles.Cast <PString> ().Where(p => p.Value != null))
            {
                var  path       = string.Format("Applications/{0}/{1}", Path.GetFileName(AppBundleDir.ItemSpec), icon.Value);
                bool addDefault = true;

                if (path.EndsWith(".png", StringComparison.Ordinal))
                {
                    icons.Add(new PString(path));
                    continue;
                }

                if (File.Exists(Path.Combine(productsDir, path + "@3x.png")))
                {
                    icons.Add(new PString(path + "@3x.png"));
                    addDefault = false;
                }

                if (File.Exists(Path.Combine(productsDir, path + "@2x.png")))
                {
                    icons.Add(new PString(path + "@2x.png"));
                    addDefault = false;
                }

                if (addDefault || File.Exists(Path.Combine(productsDir, path + ".png")))
                {
                    icons.Add(new PString(path + ".png"));
                }
            }
        }
        public void TestMergeArrays()
        {
            var plist  = new PDictionary();
            var array0 = new PArray();

            array0.Add("item0");
            array0.Add("item1");
            array0.Add("item2");
            array0.Add("item3");

            plist.Add("CFBundleIdentifier", "com.microsoft.merge-arrays");
            plist.Add("CFArrayItems", array0);

            var array1 = new PArray();

            array1.Add("item2");
            array1.Add("item3");

            var expected = (PDictionary)plist.Clone();

            array0.RemoveAt(3);
            array0.RemoveAt(2);

            var tmp = Path.GetTempFileName();

            array1.Save(tmp);

            try {
                TestExecuteTask(plist, PropertyListEditorAction.Merge, ":CFArrayItems", null, tmp, expected);
            } finally {
                File.Delete(tmp);
            }
        }
예제 #3
0
        public void TestDeleteNestedProperty()
        {
            var plist   = new PDictionary();
            var primary = new PDictionary();
            var icons   = new PDictionary();
            var files   = new PArray();

            plist.Add("CFBundleIdentifier", "com.microsoft.delete-nested-property");
            plist.Add("CFBundleIcons", icons);
            icons.Add("CFBundlePrimaryIcon", primary);
            primary.Add("CFBundleIconFiles", files);
            files.Add("icon0");
            files.Add("icon1");

            var expected = (PDictionary)plist.Clone();

            files.Add("icon2");

            TestExecuteTask(plist, PropertyListEditorAction.Delete, ":CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles:2", null, null, expected);

            var plist2 = (PDictionary)expected.Clone();

            files.Remove();

            var expected2 = (PDictionary)plist.Clone();

            TestExecuteTask(plist2, PropertyListEditorAction.Delete, ":CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles", null, null, expected2);
        }
예제 #4
0
        public void TestMergeRoot()
        {
            var expected = new PDictionary();
            var primary  = new PDictionary();
            var icons    = new PDictionary();
            var files    = new PArray();

            expected.Add("CFBundleIdentifier", "com.microsoft.merge-root");
            expected.Add("CFBundleIcons", icons);
            icons.Add("CFBundlePrimaryIcon", primary);
            primary.Add("UIPrerenderedIcon", new PBoolean(true));
            primary.Add("CFBundleIconFiles", files);
            files.Add("icon0");
            files.Add("icon1");
            files.Add("icon2");

            var plist = (PDictionary)expected.Clone();

            plist.Remove("CFBundleIcons");

            var merge = (PDictionary)expected.Clone();

            merge.Remove("CFBundleIdentifier");

            var tmp = Path.Combine(Cache.CreateTemporaryDirectory(), "tmpfile");

            merge.Save(tmp);

            TestExecuteTask(plist, PropertyListEditorAction.Merge, null, null, tmp, expected);
        }
예제 #5
0
        public void TestMergeArrays()
        {
            var plist  = new PDictionary();
            var array0 = new PArray();

            array0.Add("item0");
            array0.Add("item1");
            array0.Add("item2");
            array0.Add("item3");

            plist.Add("CFBundleIdentifier", "com.microsoft.merge-arrays");
            plist.Add("CFArrayItems", array0);

            var array1 = new PArray();

            array1.Add("item2");
            array1.Add("item3");

            var expected = (PDictionary)plist.Clone();

            array0.RemoveAt(3);
            array0.RemoveAt(2);

            var tmp = Path.Combine(Cache.CreateTemporaryDirectory(), "tmpfile");

            array1.Save(tmp);

            TestExecuteTask(plist, PropertyListEditorAction.Merge, ":CFArrayItems", null, tmp, expected);
        }
        public void WalkScheme_Dictionary_NotPartOfScheme()
        {
            var scheme = Load(@"
<PListScheme>
	<Key name = ""key1"" type = ""Array"" arrayType = ""Dictionary"" />
</PListScheme>");

            var dict = new PDictionary();

            dict.Add("foo", new PNumber(1));

            var tree = new PArray();

            tree.Add(dict);
            tree.Add(new PNumber(1));

            var root = new PDictionary();

            root.Add("foo", tree);

            var result = PListScheme.Match(root, scheme);
            var keys   = result.Keys.ToArray();

            for (int i = 0; i < keys.Length; i++)
            {
                Assert.IsNull(result [keys [i]], "#1." + i);
            }
        }
예제 #7
0
        public static void SetUISupportedInterfaceOrientations(this PDictionary dict, bool ipad, IPhoneOrientation orientations)
        {
            var key = ipad ? ManifestKeys.UISupportedInterfaceOrientationsIPad : ManifestKeys.UISupportedInterfaceOrientations;

            if (orientations == IPhoneOrientation.None)
            {
                dict.Remove(key);
                return;
            }

            var arr = new PArray();

            if ((orientations & IPhoneOrientation.Portrait) != 0)
            {
                arr.Add(IPhoneOrientationStrings.Up);
            }
            if ((orientations & IPhoneOrientation.UpsideDown) != 0)
            {
                arr.Add(IPhoneOrientationStrings.Down);
            }
            if ((orientations & IPhoneOrientation.LandscapeLeft) != 0)
            {
                arr.Add(IPhoneOrientationStrings.Left);
            }
            if ((orientations & IPhoneOrientation.LandscapeRight) != 0)
            {
                arr.Add(IPhoneOrientationStrings.Right);
            }

            dict[key] = arr;
        }
예제 #8
0
        static PDictionary CreateIndex()
        {
            var plist    = new PDictionary();
            var profiles = new PArray();

            foreach (var fileName in Directory.EnumerateFiles(MobileProvision.ProfileDirectory))
            {
                if (!fileName.EndsWith(".mobileprovision", StringComparison.Ordinal) && !fileName.EndsWith(".provisionprofile", StringComparison.Ordinal))
                {
                    continue;
                }

                try {
                    var profile = CreateIndexRecord(fileName);
                    profiles.Add(profile);
                } catch (Exception ex) {
                    LoggingService.LogWarning("Error reading provisioning profile '{0}': {1}", fileName, ex);
                }
            }

            plist.Add("Version", new PNumber(IndexVersion));
            plist.Add("LastModified", new PDate(Directory.GetLastWriteTimeUtc(MobileProvision.ProfileDirectory)));
            plist.Add("ProvisioningProfiles", profiles);

            Save(plist);

            return(plist);
        }
예제 #9
0
        public void TestAddNestedProperty()
        {
            var plist = new PDictionary();

            plist.Add("CFBundleIdentifier", "com.microsoft.add-nested-property");

            var expected = (PDictionary)plist.Clone();
            var primary  = new PDictionary();
            var icons    = new PDictionary();

            primary.Add("UIPrerenderedIcon", new PBoolean(true));
            icons.Add("CFBundlePrimaryIcon", primary);
            expected.Add("CFBundleIcons", icons);

            TestExecuteTask(plist, PropertyListEditorAction.Add, ":CFBundleIcons:CFBundlePrimaryIcon:UIPrerenderedIcon", "bool", "true", expected);

            plist = (PDictionary)expected.Clone();
            var files = new PArray();

            primary.Add("CFBundleIconFiles", files);

            TestExecuteTask(plist, PropertyListEditorAction.Add, ":CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles", "array", null, expected);

            plist = (PDictionary)expected.Clone();
            files.Add("icon0");

            TestExecuteTask(plist, PropertyListEditorAction.Add, ":CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles:", "string", "icon0", expected);
        }
        public void AvailableValues_Number_InArray()
        {
            var scheme = Load(@"
<PListScheme>
	<Key name = ""keyname"" type = ""Array"" arrayType = ""Number"" >
		<Value name = ""1"" />
		<Value name = ""2"" />
		<Value name = ""3"" />
	</Key>
</PListScheme>");

            var array = new PArray();
            var key   = scheme.GetKey("keyname");

            var root = new PDictionary();

            root.Add(key.Identifier, array);

            // The array cannot have a different value (it's a container!)
            var available = PListScheme.AvailableValues(array, PListScheme.Match(root, scheme));

            Assert.IsNull(available, "#1");

            // The only element in the array can be of value '1', '2' or '3'
            array.Add(new PNumber(1));
            available = PListScheme.AvailableValues(array [0], PListScheme.Match(root, scheme));
            Assert.AreEqual(3, available.Count, "#2");

            // Now the first element can be either a '1' or a '3'
            array.Add(new PNumber(2));
            available = PListScheme.AvailableValues(array [0], PListScheme.Match(root, scheme));
            Assert.AreEqual(2, available.Count, "#3");
            Assert.IsTrue(available.Any(f => f.Identifier == "1"), "#4");
            Assert.IsTrue(available.Any(f => f.Identifier == "3"), "#5");

            // The second element can be a '2' or a '3'
            available = PListScheme.AvailableValues(array [1], PListScheme.Match(root, scheme));
            Assert.AreEqual(2, available.Count, "#6");
            Assert.IsTrue(available.Any(f => f.Identifier == "2"), "#7");
            Assert.IsTrue(available.Any(f => f.Identifier == "3"), "#8");

            // Now it can only be '1'
            array.Add(new PNumber(3));
            available = PListScheme.AvailableValues(array [0], PListScheme.Match(root, scheme));
            Assert.AreEqual(1, available.Count, "#9");
            Assert.IsTrue(available.Any(f => f.Identifier == "1"), "#10");
        }
예제 #11
0
			public PObject Create ()
			{
				if (Type == PDictionary.Type) {
					var dictionary = new PDictionary ();
					foreach (var v in Values) {
						if (v.Required)
							dictionary.Add (v.Identifier, v.Create ());
					}
					
					// If nothing was required, create an initial one anyway
					if (dictionary.Count == 0) {
						var first = Values.FirstOrDefault ();
						if (first == null) {
							dictionary.Add ("newNode", PObject.Create (PString.Type));
						} else {
							dictionary.Add (first.Identifier ?? "newNode", first.Create ());
						}
					}
					return dictionary;
				} else if (Type == PArray.Type) {
					var array = new PArray ();
					foreach (var v in Values) {
						if (v.Required)
							array.Add (v.Create ());
					}
					
					// If nothing was required, create an initial one anyway
					if (array.Count == 0) {
						var first = Values.FirstOrDefault ();
						if (first == null) {
							array.Add (PObject.Create (ArrayType));
						} else {
							array.Add (first.Create ());
						}
					}
					return array;
				} else if (Values.Any ()){
					return Values.First ().Create ();
				} else {
					var obj = PObject.Create (Type);
					if (!string.IsNullOrEmpty (Identifier) && !(this is Key))
						obj.SetValue (Identifier);
					return obj;
				}
			}
예제 #12
0
        static PArray CreateKnownSdkVersionsArray(IList <MacOSXSdkVersion> versions)
        {
            var array = new PArray();

            for (int i = 0; i < versions.Count; i++)
            {
                array.Add(new PString(versions[i].ToString()));
            }

            return(array);
        }
예제 #13
0
        static PDictionary CreateIndexRecord(string fileName)
        {
            var     provision    = MobileProvision.LoadFromFile(fileName);
            var     certificates = new PArray();
            var     record       = new PDictionary();
            PString identifier;

            record.Add("FileName", new PString(Path.GetFileName(fileName)));
            record.Add("LastModified", new PDate(File.GetLastWriteTimeUtc(fileName)));

            record.Add("Name", new PString(provision.Name));
            record.Add("Uuid", new PString(provision.Uuid));
            record.Add("Distribution", new PString(provision.DistributionType.ToString()));
            record.Add("CreationDate", new PDate(provision.CreationDate.ToUniversalTime()));
            record.Add("ExpirationDate", new PDate(provision.ExpirationDate.ToUniversalTime()));

            var platforms = new PArray();

            if (provision.Platforms != null)
            {
                for (int i = 0; i < provision.Platforms.Length; i++)
                {
                    platforms.Add(new PString(provision.Platforms[i].ToString()));
                }
            }
            record.Add("Platforms", platforms);

            if (provision.Entitlements.TryGetValue("com.apple.application-identifier", out identifier))
            {
                record.Add("ApplicationIdentifier", new PString(identifier.Value));
            }
            else if (provision.Entitlements.TryGetValue("application-identifier", out identifier))
            {
                record.Add("ApplicationIdentifier", new PString(identifier.Value));
            }
            else
            {
                record.Add("ApplicationIdentifier", new PString(string.Empty));
            }

            foreach (var certificate in provision.DeveloperCertificates)
            {
                var info = new PDictionary();

                info.Add("Name", new PString(Keychain.GetCertificateCommonName(certificate)));
                info.Add("Thumbprint", new PString(certificate.Thumbprint));

                certificates.Add(info);
            }

            record.Add("DeveloperCertificates", certificates);

            return(record);
        }
예제 #14
0
        public void TestAddArrayValue()
        {
            var plist   = new PDictionary();
            var primary = new PDictionary();
            var icons   = new PDictionary();
            var files   = new PArray();

            plist.Add("CFBundleIdentifier", "com.microsoft.add-array-value");
            plist.Add("CFBundleIcons", icons);
            icons.Add("CFBundlePrimaryIcon", primary);
            primary.Add("CFBundleIconFiles", files);
            files.Add("icon0");
            files.Add("icon1");
            files.Add("icon2");

            var expected = (PDictionary)plist.Clone();

            files.RemoveAt(0);

            TestExecuteTask(plist, PropertyListEditorAction.Add, ":CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles:0", "string", "icon0", expected);
        }
        public void AvailableKeys_NumberArray()
        {
            var scheme = Load(@"
<PListScheme>
	<Key name = ""keyname"" type = ""Array"" arrayType = ""Number"" >
		<Value name = ""1"" />
		<Value name = ""2"" />
		<Value name = ""3"" />
	</Key>
</PListScheme>");

            var key   = scheme.GetKey("keyname");
            var value = new PArray();

            var root = new PDictionary();

            root.Add(key.Identifier, value);

            var available = PListScheme.AvailableKeys(value, PListScheme.Match(root, scheme));

            Assert.AreEqual(3, available.Count, "#1");

            // We can create '2' or '3', but not '1'
            value.Add(new PNumber(1));
            available = PListScheme.AvailableKeys(value, PListScheme.Match(root, scheme));
            Assert.AreEqual(2, available.Count, "#2");
            Assert.IsFalse(available.Any(v => v.Identifier == "1"), "#3");

            // We can only create '3'
            value.Add(new PNumber(2));
            available = PListScheme.AvailableKeys(value, PListScheme.Match(root, scheme));
            Assert.AreEqual(1, available.Count, "#4");
            Assert.IsTrue(available.Any(v => v.Identifier == "3"), "#5");

            value.Add(new PNumber(3));
            available = PListScheme.AvailableKeys(value, PListScheme.Match(root, scheme));
            Assert.AreEqual(0, available.Count, "#4");
        }
예제 #16
0
        static PArray CreateDefaultFeatures(MacOSXSdkVersion version)
        {
            var features = new PArray();

            if (version >= new MacOSXSdkVersion(1, 9, 0))
            {
                features.Add(new PString("activation"));
            }
            if (version >= new MacOSXSdkVersion(1, 11, 0) && version < new MacOSXSdkVersion(2, 5, 0))
            {
                features.Add(new PString("ref-counting"));
            }
            if (version >= new MacOSXSdkVersion(2, 5, 0))
            {
                features.Add(new PString("modern-http-client"));
            }
            if (version >= new MacOSXSdkVersion(2, 10, 0))
            {
                features.Add(new PString("mono-symbol-archive"));
            }

            return(features);
        }
        public void WalkScheme_Array_NotPartOfScheme()
        {
            var scheme = Load(@"
<PListScheme>
	<Key name = ""key1"" type = ""Array"" arrayType = ""Dictionary"" />
</PListScheme>");

            var tree = new PArray();

            tree.Add(new PNumber(0));
            tree.Add(new PNumber(1));

            var root = new PDictionary();

            root.Add("foo", tree);

            var result = PListScheme.Match(root, scheme);

            Assert.AreEqual(3, result.Count, "#1");
            Assert.IsNull(result [tree], "#2");
            Assert.IsNull(result [tree [0]], "#3");
            Assert.IsNull(result [tree [1]], "#4");
        }
예제 #18
0
        public void TestSetArrayValue()
        {
            var plist   = new PDictionary();
            var primary = new PDictionary();
            var icons   = new PDictionary();
            var files   = new PArray();

            plist.Add("CFBundleIdentifier", "com.microsoft.set-array-value");
            plist.Add("CFBundleIcons", icons);
            icons.Add("CFBundlePrimaryIcon", primary);
            primary.Add("CFBundleIconFiles", files);
            files.Add("icon0");
            files.Add("icon1");
            files.Add("icon2");

            var expected = (PDictionary)plist.Clone();

            files[0] = new PString("icon");

            TestExecuteTask(plist, PropertyListEditorAction.Set, ":CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles:0", "string", "icon0", expected);

            // Note: this will fail due to the index being out of range
            TestExecuteTask(plist, PropertyListEditorAction.Set, ":CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles:3", "string", "icon3", null);
        }
예제 #19
0
        public static void SetUIDeviceFamily(this PDictionary dict, IPhoneDeviceType deviceTypes)
        {
            if (deviceTypes == IPhoneDeviceType.NotSet)
            {
                dict.Remove(ManifestKeys.UIDeviceFamily);
                return;
            }

            PArray arr = new PArray();

            foreach (var family in deviceTypes.ToDeviceFamily())
            {
                arr.Add(new PNumber((int)family));
            }

            dict[ManifestKeys.UIDeviceFamily] = arr;
        }
예제 #20
0
        PArray MergeEntitlementArray(PArray array, MobileProvision profile)
        {
            var result = new PArray();

            foreach (var item in array)
            {
                PObject value;

                if (item is PDictionary)
                {
                    value = MergeEntitlementDictionary((PDictionary)item, profile);
                }
                else if (item is PString)
                {
                    value = MergeEntitlementString((PString)item, profile, false);
                }
                else if (item is PArray)
                {
                    value = MergeEntitlementArray((PArray)item, profile);
                }
                else
                {
                    value = item.Clone();
                }

                if (value != null)
                {
                    result.Add(value);
                }
            }

            if (result.Count > 0)
            {
                return(result);
            }

            return(null);
        }
예제 #21
0
        public override bool Execute()
        {
            var intermediate          = Path.Combine(IntermediateOutputPath, ToolName);
            var intermediateBundleDir = Path.Combine(intermediate, "bundle");
            var intermediateCloneDir  = Path.Combine(intermediate, "cloned-assets");
            var manifest        = new TaskItem(Path.Combine(intermediate, "asset-manifest.plist"));
            var bundleResources = new List <ITaskItem> ();
            var outputManifests = new List <ITaskItem> ();
            var catalogs        = new List <ITaskItem> ();
            var unique          = new HashSet <string> ();

            var knownSpecs = new HashSet <string> ();
            var clones     = new HashSet <string> ();
            var items      = new List <ITaskItem> ();
            var specs      = new PArray();

            if (AppManifest != null)
            {
                try {
                    plist = PDictionary.FromFile(AppManifest.ItemSpec);
                } catch (Exception ex) {
                    Log.LogError(null, null, null, AppManifest.ItemSpec, 0, 0, 0, 0, "{0}", ex.Message);
                    return(false);
                }
            }

            for (int i = 0; i < ImageAssets.Length; i++)
            {
                var vpath = BundleResource.GetVirtualProjectPath(ProjectDir, ImageAssets[i], !string.IsNullOrEmpty(SessionId));

                // Ignore MacOS .DS_Store files...
                if (Path.GetFileName(vpath).Equals(".DS_Store", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
                var catalog = Path.GetDirectoryName(vpath);

                // keep walking up the directory structure until we get to the .xcassets directory
                while (!string.IsNullOrEmpty(catalog) && Path.GetExtension(catalog) != ".xcassets")
                {
                    catalog = Path.GetDirectoryName(catalog);
                }

                if (string.IsNullOrEmpty(catalog))
                {
                    Log.LogWarning(null, null, null, ImageAssets[i].ItemSpec, 0, 0, 0, 0, MSBStrings.W0090, ImageAssets[i].ItemSpec);
                    continue;
                }

                if (ImageAssets[i].GetMetadata("Link") != null)
                {
                    // Note: if any of the files within a catalog are linked, we'll have to clone the *entire* catalog
                    clones.Add(catalog);
                    continue;
                }

                // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
                if (Path.GetFileName(vpath) != "Contents.json")
                {
                    continue;
                }

                items.Add(ImageAssets[i]);
            }

            // clone any *.xcassets dirs that need cloning
            if (clones.Count > 0)
            {
                if (Directory.Exists(intermediateCloneDir))
                {
                    Directory.Delete(intermediateCloneDir, true);
                }

                Directory.CreateDirectory(intermediateCloneDir);

                items.Clear();

                for (int i = 0; i < ImageAssets.Length; i++)
                {
                    var       vpath = BundleResource.GetVirtualProjectPath(ProjectDir, ImageAssets[i], !string.IsNullOrEmpty(SessionId));
                    var       clone = false;
                    ITaskItem item;

                    // Ignore MacOS .DS_Store files...
                    if (Path.GetFileName(vpath).Equals(".DS_Store", StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    foreach (var catalog in clones)
                    {
                        if (vpath.Length > catalog.Length && vpath[catalog.Length] == '/' && vpath.StartsWith(catalog, StringComparison.Ordinal))
                        {
                            clone = true;
                            break;
                        }
                    }

                    if (clone)
                    {
                        var src = ImageAssets[i].GetMetadata("FullPath");

                        if (!File.Exists(src))
                        {
                            Log.LogError(null, null, null, src, 0, 0, 0, 0, MSBStrings.E0091, src);
                            return(false);
                        }

                        var dest = Path.Combine(intermediateCloneDir, vpath);
                        var dir  = Path.GetDirectoryName(dest);

                        Directory.CreateDirectory(dir);

                        File.Copy(src, dest, true);

                        // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
                        if (Path.GetFileName(vpath) != "Contents.json")
                        {
                            continue;
                        }

                        item = new TaskItem(dest);
                        ImageAssets[i].CopyMetadataTo(item);
                        item.SetMetadata("Link", vpath);
                    }
                    else
                    {
                        // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
                        if (Path.GetFileName(vpath) != "Contents.json")
                        {
                            continue;
                        }

                        item = ImageAssets[i];
                    }

                    items.Add(item);
                }
            }

            // Note: `items` contains only the Contents.json files at this point
            for (int i = 0; i < items.Count; i++)
            {
                var vpath = BundleResource.GetVirtualProjectPath(ProjectDir, items[i], !string.IsNullOrEmpty(SessionId));
                var path  = items[i].GetMetadata("FullPath");

                // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
                var catalog = Path.GetDirectoryName(vpath);
                path = Path.GetDirectoryName(path);

                // keep walking up the directory structure until we get to the .xcassets directory
                while (!string.IsNullOrEmpty(catalog) && Path.GetExtension(catalog) != ".xcassets")
                {
                    catalog = Path.GetDirectoryName(catalog);
                    path    = Path.GetDirectoryName(path);
                }

                if (unique.Add(catalog))
                {
                    var item = new TaskItem(path);
                    item.SetMetadata("Link", catalog);

                    catalogs.Add(item);
                }

                if (AppleSdkSettings.XcodeVersion.Major >= 7 && SdkPlatform != "WatchSimulator")
                {
                    var text = File.ReadAllText(items[i].ItemSpec);

                    if (string.IsNullOrEmpty(text))
                    {
                        continue;
                    }

                    JsonDocument json;
                    JsonElement  value;

                    try {
                        var options = new JsonDocumentOptions()
                        {
                            AllowTrailingCommas = true,
                        };
                        json = JsonDocument.Parse(text, options);
                    } catch (JsonException je) {
                        var line = (int)(je.LineNumber + 1 ?? 0);
                        var col  = (int)(je.BytePositionInLine + 1 ?? 0);
                        Log.LogError(null, null, null, items [i].ItemSpec, line, col, line, col, "{0}", je.Message);
                        return(false);
                    } catch (Exception e) {
                        Log.LogError(null, null, null, items[i].ItemSpec, 0, 0, 0, 0, MSBStrings.E0092, e.Message);
                        return(false);
                    }

                    if (!json.RootElement.TryGetProperty("properties", out value) || value.ValueKind != JsonValueKind.Object)
                    {
                        continue;
                    }

                    var properties = value;

                    if (!properties.TryGetProperty("on-demand-resource-tags", out value) || value.ValueKind != JsonValueKind.Array)
                    {
                        continue;
                    }

                    var    resourceTags = value;
                    var    tags         = new HashSet <string> ();
                    string hash;

                    foreach (var tag in resourceTags.EnumerateArray())
                    {
                        if (tag.ValueKind == JsonValueKind.String)
                        {
                            tags.Add(tag.GetString());
                        }
                    }

                    var tagList = tags.ToList();
                    tagList.Sort();

                    var assetDir = AssetPackUtils.GetAssetPackDirectory(intermediate, BundleIdentifier, tagList, out hash);

                    if (knownSpecs.Add(hash))
                    {
                        var assetpack = new PDictionary();
                        var ptags     = new PArray();

                        Directory.CreateDirectory(assetDir);

                        for (int j = 0; j < tagList.Count; j++)
                        {
                            ptags.Add(new PString(tagList[j]));
                        }

                        assetpack.Add("bundle-id", new PString(string.Format("{0}.asset-pack-{1}", BundleIdentifier, hash)));
                        assetpack.Add("bundle-path", new PString(Path.GetFullPath(assetDir)));
                        assetpack.Add("tags", ptags);
                        specs.Add(assetpack);
                    }
                }
            }

            if (catalogs.Count == 0)
            {
                // There are no (supported?) asset catalogs
                return(!Log.HasLoggedErrors);
            }

            partialAppManifest = new TaskItem(Path.Combine(intermediate, "partial-info.plist"));

            if (specs.Count > 0)
            {
                outputSpecs = Path.Combine(intermediate, "output-specifications.plist");
                specs.Save(outputSpecs, true);
            }

            Directory.CreateDirectory(intermediateBundleDir);

            // Note: Compile() will set the PartialAppManifest property if it is used...
            if ((Compile(catalogs.ToArray(), intermediateBundleDir, manifest)) != 0)
            {
                return(false);
            }

            if (PartialAppManifest != null && !File.Exists(PartialAppManifest.GetMetadata("FullPath")))
            {
                Log.LogError(MSBStrings.E0093, PartialAppManifest.GetMetadata("FullPath"));
            }

            try {
                var manifestOutput = PDictionary.FromFile(manifest.ItemSpec);

                LogWarningsAndErrors(manifestOutput, catalogs[0]);

                bundleResources.AddRange(GetCompiledBundleResources(manifestOutput, intermediateBundleDir));
                outputManifests.Add(manifest);
            } catch (Exception ex) {
                Log.LogError(MSBStrings.E0094, ToolName, manifest.ItemSpec, ex.Message);
            }

            foreach (var assetpack in specs.OfType <PDictionary> ())
            {
                var path       = Path.Combine(assetpack.GetString("bundle-path").Value, "Info.plist");
                var bundlePath = PathUtils.AbsoluteToRelative(intermediate, path);
                var outputPath = Path.Combine(OutputPath, bundlePath);
                var rpath      = Path.Combine(intermediate, bundlePath);
                var dict       = new PDictionary();

                dict.SetCFBundleIdentifier(assetpack.GetString("bundle-id").Value);
                dict.Add("Tags", assetpack.GetArray("tags").Clone());

                dict.Save(path, true, true);

                var item = new TaskItem(rpath);
                item.SetMetadata("LogicalName", bundlePath);
                item.SetMetadata("OutputPath", outputPath);
                item.SetMetadata("Optimize", "false");

                bundleResources.Add(item);
            }

            BundleResources = bundleResources.ToArray();
            OutputManifests = outputManifests.ToArray();

            return(!Log.HasLoggedErrors);
        }
예제 #22
0
        public override bool Execute()
        {
            var manifestPath = Path.Combine (AppBundleDir.ItemSpec, "AssetPackManifestTemplate.plist");
            var onDemandResourcesPath = Path.Combine (AppBundleDir.ItemSpec, "OnDemandResources.plist");
            var onDemandResourcesDir = Path.Combine (OutputPath, "OnDemandResources");
            var onDemandResourcesStamp = File.GetLastWriteTime (onDemandResourcesPath);
            var initialInstallTags = new HashSet<string> (AssetPackUtils.ParseTags (InitialInstallTags));
            var prefetchOrder = AssetPackUtils.ParseTags (PrefetchOrder);
            var manifestStamp = File.GetLastWriteTime (manifestPath);
            var onDemandResources = new PDictionary ();
            var requestTags = new PDictionary ();
            bool updateOnDemandResources = false;
            var assetPacks = new PDictionary ();
            var manifest = new PDictionary ();
            var resources = new PArray ();
            bool updateManifest = false;

            Log.LogTaskName ("CreateAssetPackManifest");
            Log.LogTaskProperty ("AppBundleDir", AppBundleDir);
            Log.LogTaskProperty ("InitialInstallTags", InitialInstallTags);
            Log.LogTaskProperty ("OutputPath", OutputPath);
            Log.LogTaskProperty ("PrefetchOrder", PrefetchOrder);

            if (!Directory.Exists (onDemandResourcesDir))
                return !Log.HasLoggedErrors;

            onDemandResources.Add ("NSBundleResourceRequestAssetPacks", assetPacks);
            onDemandResources.Add ("NSBundleResourceRequestTags", requestTags);

            manifest.Add ("resources", resources);

            foreach (var dir in Directory.EnumerateDirectories (onDemandResourcesDir)) {
                var path = Path.Combine (dir, "Info.plist");
                PDictionary info;

                if (!File.Exists (path))
                    continue;

                var mtime = File.GetLastWriteTime (path);

                updateOnDemandResources = updateOnDemandResources || mtime > onDemandResourcesStamp;
                updateManifest = updateManifest || mtime > manifestStamp;

                try {
                    info = PDictionary.FromFile (path);
                } catch {
                    continue;
                }

                var bundleIdentifier = info.GetCFBundleIdentifier ();
                var primaryContentHash = new PDictionary ();
                var resource = new PDictionary ();
                var items = new PArray ();
                long size = 0;

                // update OnDemandResources.plist:NSBundleResourceRequestAssetPacks
                foreach (var file in Directory.EnumerateFiles (dir)) {
                    var name = Path.GetFileName (file);

                    if (name != "Info.plist")
                        items.Add (new PString (name));

                    size += new FileInfo (file).Length;
                }

                assetPacks.Add (bundleIdentifier, items);

                // update OnDemandResources.plist:NSBundleResourceRequestTags
                var tags = info.GetArray ("Tags").OfType<PString> ().Select (x => x.Value);
                var priority = double.NaN;

                foreach (var tag in tags) {
                    PDictionary dict;
                    PArray packs;

                    if (initialInstallTags.Contains (tag)) {
                        priority = 1.0f;
                    } else {
                        for (int i = 0; i < prefetchOrder.Length; i++) {
                            if (tag == prefetchOrder[i]) {
                                var value = GetDownloadPriority (i, prefetchOrder.Length);

                                priority = double.IsNaN (priority) ? value : Math.Max (priority, value);
                                break;
                            }
                        }
                    }

                    if (!requestTags.TryGetValue (tag, out dict)) {
                        dict = new PDictionary ();
                        dict.Add ("NSAssetPacks", new PArray ());

                        requestTags.Add (tag, dict);
                    }

                    packs = dict.GetArray ("NSAssetPacks");

                    packs.Add (new PString (bundleIdentifier));
                }

                // update AssetPackManifestTemplate.plist
                resource.Add ("URL", new PString ("http://127.0.0.1" + Path.GetFullPath (dir)));
                resource.Add ("bundleKey", new PString (bundleIdentifier));

                if (!double.IsNaN (priority))
                    resource.Add ("downloadPriority", new PReal (priority));

                resource.Add ("isStreamable", new PBoolean (true));
                primaryContentHash.Add ("hash", mtime.ToString ("yyyy-MM-dd HH:mm:ss.000"));
                primaryContentHash.Add ("strategy", "modtime");
                resource.Add ("primaryContentHash", primaryContentHash);
                resource.Add ("uncompressedSize", new PNumber ((int) ((size + 8191) & ~8191)));

                resources.Add (resource);
            }

            if (updateOnDemandResources) {
                try {
                    onDemandResources.Save (onDemandResourcesPath, true, true);
                } catch (Exception ex) {
                    Log.LogError ("Error saving `{0}': {1}", onDemandResourcesPath, ex.Message);
                }
            }

            if (updateManifest) {
                try {
                    manifest.Save (manifestPath, true, true);
                } catch (Exception ex) {
                    Log.LogError ("Error saving `{0}': {1}", manifestPath, ex.Message);
                }
            }

            return !Log.HasLoggedErrors;
        }
예제 #23
0
        bool Compile(PDictionary plist)
        {
            var currentSDK = IPhoneSdks.GetSdk(Framework);

            if (!currentSDK.SdkIsInstalled(sdkVersion, SdkIsSimulator))
            {
                Log.LogError(null, null, null, null, 0, 0, 0, 0, "The {0} SDK for '{1}' is not installed.", Framework, sdkVersion);
                return(false);
            }

            supportedDevices = plist.GetUIDeviceFamily();

            if (!IsWatchApp)
            {
                var version = IPhoneSdks.MonoTouch.ExtendedVersion;
                // This key is our supported way of determining if an app
                // was built with Xamarin, so it needs to be present in all apps.

                var dict = new PDictionary();
                dict.Add("Version", new PString(string.Format("{0} ({1}: {2})", version.Version, version.Branch, version.Hash)));
                plist.Add("com.xamarin.ios", dict);
            }

            var sdkSettings = currentSDK.GetSdkSettings(sdkVersion, SdkIsSimulator);
            var dtSettings  = currentSDK.GetDTSettings();

            SetValue(plist, ManifestKeys.BuildMachineOSBuild, dtSettings.BuildMachineOSBuild);
            plist.SetIfNotPresent(ManifestKeys.CFBundleDevelopmentRegion, "en");

            plist.SetIfNotPresent(ManifestKeys.CFBundleExecutable, AssemblyName);
            if (IsIOS)
            {
                var executable = plist.GetCFBundleExecutable();
                if (executable.EndsWith(".app", StringComparison.Ordinal))
                {
                    LogAppManifestError("The executable (CFBundleExecutable) name ({0}) cannot end with '.app', because iOS may fail to launch the app.", executable);
                }
            }

            if (IsIOS)
            {
                if (minimumOSVersion < IPhoneSdkVersion.V5_0 && plist.GetUIMainStoryboardFile(true) != null)
                {
                    LogAppManifestError("Applications using a storyboard as the Main Interface must have a deployment target greater than 5.0");
                }

                if (!plist.ContainsKey(ManifestKeys.CFBundleName))
                {
                    plist [ManifestKeys.CFBundleName] = plist.ContainsKey(ManifestKeys.CFBundleDisplayName) ? plist.GetString(ManifestKeys.CFBundleDisplayName).Clone() : new PString(AppBundleName);
                }
            }
            else
            {
                plist.SetIfNotPresent(ManifestKeys.CFBundleName, AppBundleName);
            }

            plist.SetIfNotPresent(ManifestKeys.CFBundleIdentifier, BundleIdentifier);
            plist.SetIfNotPresent(ManifestKeys.CFBundleInfoDictionaryVersion, "6.0");
            plist.SetIfNotPresent(ManifestKeys.CFBundlePackageType, IsAppExtension ? "XPC!" : "APPL");
            if (!string.IsNullOrEmpty(ResourceRules))
            {
                plist.SetIfNotPresent(ManifestKeys.CFBundleResourceSpecification, Path.GetFileName(ResourceRules));
            }
            plist.SetIfNotPresent(ManifestKeys.CFBundleSignature, "????");
            if (!plist.ContainsKey(ManifestKeys.CFBundleSupportedPlatforms))
            {
                plist[ManifestKeys.CFBundleSupportedPlatforms] = new PArray {
                    SdkPlatform
                }
            }
            ;
            plist.SetIfNotPresent(ManifestKeys.CFBundleVersion, "1.0");
            plist.SetIfNotPresent(ManifestKeys.CFBundleShortVersionString, plist.GetCFBundleVersion());

            if (!SdkIsSimulator)
            {
                SetValue(plist, "DTCompiler", sdkSettings.DTCompiler);
                SetValue(plist, "DTPlatformBuild", dtSettings.DTPlatformBuild);
                SetValue(plist, "DTSDKBuild", sdkSettings.DTSDKBuild);
            }

            plist.SetIfNotPresent("DTPlatformName", SdkPlatform.ToLowerInvariant());
            if (!SdkIsSimulator)
            {
                SetValue(plist, "DTPlatformVersion", dtSettings.DTPlatformVersion);
            }

            var sdkName = sdkSettings.CanonicalName;

            // older sdksettings didn't have a canonicalname for sim
            if (SdkIsSimulator && string.IsNullOrEmpty(sdkName))
            {
                var deviceSdkSettings = currentSDK.GetSdkSettings(sdkVersion, false);
                sdkName = deviceSdkSettings.AlternateSDK;
            }
            SetValue(plist, "DTSDKName", sdkName);

            if (!SdkIsSimulator)
            {
                SetValue(plist, "DTXcode", AppleSdkSettings.DTXcode);
                SetValue(plist, "DTXcodeBuild", dtSettings.DTXcodeBuild);
            }

            SetDeviceFamily(plist);

            plist.SetIfNotPresent(ManifestKeys.MinimumOSVersion, minimumOSVersion.ToString());

            if (IsWatchExtension)
            {
                // Note: Only watchOS1 Extensions target Xamarin.iOS
                if (Framework == PlatformFramework.iOS)
                {
                    PObject value;

                    if (!plist.TryGetValue(ManifestKeys.UIRequiredDeviceCapabilities, out value))
                    {
                        var capabilities = new PArray();
                        capabilities.Add(new PString("watch-companion"));

                        plist.Add(ManifestKeys.UIRequiredDeviceCapabilities, capabilities);
                    }
                    else if (value is PDictionary)
                    {
                        var capabilities = (PDictionary)value;

                        if (!capabilities.ContainsKey("watch-companion"))
                        {
                            capabilities.Add("watch-companion", new PBoolean(true));
                        }
                    }
                    else
                    {
                        var  capabilities = (PArray)value;
                        bool exists       = false;

                        foreach (var capability in capabilities.OfType <PString> ())
                        {
                            if (capability.Value != "watch-companion")
                            {
                                continue;
                            }

                            exists = true;
                            break;
                        }

                        if (!exists)
                        {
                            capabilities.Add(new PString("watch-companion"));
                        }
                    }
                }

                if (Debug)
                {
                    SetAppTransportSecurity(plist);
                }
            }

            // Remove any Xamarin Studio specific keys
            plist.Remove(ManifestKeys.XSLaunchImageAssets);
            plist.Remove(ManifestKeys.XSAppIconAssets);

            // Merge with any partial plists generated by the Asset Catalog compiler...
            MergePartialPlistTemplates(plist);

            if (IsIOS)
            {
                Validation(plist);
            }

            CompiledAppManifest = new TaskItem(Path.Combine(AppBundleDir, "Info.plist"));
            plist.Save(CompiledAppManifest.ItemSpec, true, true);

            return(!Log.HasLoggedErrors);
        }

        void SetDeviceFamily(PDictionary plist)
        {
            switch (Framework)
            {
            case PlatformFramework.iOS:
                SetIOSDeviceFamily(plist);
                break;

            case PlatformFramework.WatchOS:
                plist.SetUIDeviceFamily(IPhoneDeviceType.Watch);
                break;

            case PlatformFramework.TVOS:
                plist.SetUIDeviceFamily(IPhoneDeviceType.TV);
                break;
            }
        }

        void SetIOSDeviceFamily(PDictionary plist)
        {
            if (IsWatchApp)
            {
                if (SdkIsSimulator)
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.IPhone | IPhoneDeviceType.Watch);
                }
                else
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.Watch);
                }
            }
            else
            {
                if (!IsAppExtension)
                {
                    plist.SetIfNotPresent(ManifestKeys.LSRequiresIPhoneOS, true);
                }

                if (minimumOSVersion >= IPhoneSdkVersion.V3_2 && supportedDevices == IPhoneDeviceType.NotSet)
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.IPhone);
                }
            }
        }

        void SetAppTransportSecurity(PDictionary plist)
        {
            // Debugging over http has a couple of gotchas:
            // * We can't use https, because that requires a valid server certificate,
            //   which we can't ensure.
            //   It would also require a hostname for the mac, which it might not have either.
            // * NSAppTransportSecurity/NSExceptionDomains does not allow exceptions based
            //   on IP address (only hostname).
            // * Which means the only way to make sure watchOS allows connections from
            //   the app on device to the mac is to disable App Transport Security altogether.
            // Good news: watchOS 3 will apparently not apply ATS when connecting
            // directly to IP addresses, which means we won't have to do this at all
            // (sometime in the future).

            PDictionary ats;

            if (!plist.TryGetValue(ManifestKeys.NSAppTransportSecurity, out ats))
            {
                plist.Add(ManifestKeys.NSAppTransportSecurity, ats = new PDictionary());
            }

            if (ats.GetBoolean(ManifestKeys.NSAllowsArbitraryLoads))
            {
                Log.LogMessage(MessageImportance.Low, "All http loads are already allowed.");
            }
            else
            {
                Log.LogMessage(MessageImportance.Low, "Allowed arbitrary HTTP loads to support debugging.");
                ats.SetBooleanOrRemove(ManifestKeys.NSAllowsArbitraryLoads, true);
            }
        }

        void Validation(PDictionary plist)
        {
            var supportsIPhone = (supportedDevices & IPhoneDeviceType.IPhone) != 0 ||
                                 supportedDevices == IPhoneDeviceType.NotSet;
            var supportsIPad = (supportedDevices & IPhoneDeviceType.IPad) != 0;

            // Validation...
            if (!IsAppExtension && sdkVersion >= IPhoneSdkVersion.V3_2)
            {
                IPhoneOrientation orientation;

                if (supportsIPhone)
                {
                    orientation = plist.GetUISupportedInterfaceOrientations(false);
                    if (orientation == IPhoneOrientation.None)
                    {
                        LogAppManifestWarning("Supported iPhone orientations have not been set");
                    }
                    else if (!orientation.IsValidPair())
                    {
                        LogAppManifestWarning("Supported iPhone orientations are not matched pairs");
                    }
                }

                if (supportsIPad)
                {
                    orientation = plist.GetUISupportedInterfaceOrientations(true);
                    if (orientation == IPhoneOrientation.None)
                    {
                        LogAppManifestWarning("Supported iPad orientations have not been set");
                    }
                    else if (!orientation.IsValidPair())
                    {
                        LogAppManifestWarning("Supported iPad orientations are not matched pairs");
                    }
                }
            }
        }
    }
예제 #24
0
        public override bool Execute()
        {
            var    intermediate          = Path.Combine(IntermediateOutputPath, ToolName);
            var    intermediateBundleDir = Path.Combine(intermediate, "bundle");
            var    intermediateCloneDir  = Path.Combine(intermediate, "cloned-assets");
            var    manifest         = new TaskItem(Path.Combine(intermediate, "asset-manifest.plist"));
            var    bundleResources  = new List <ITaskItem> ();
            var    outputManifests  = new List <ITaskItem> ();
            var    catalogs         = new List <ITaskItem> ();
            var    unique           = new HashSet <string> ();
            string bundleIdentifier = null;
            var    knownSpecs       = new HashSet <string> ();
            var    clones           = new HashSet <string> ();
            var    items            = new List <ITaskItem> ();
            var    specs            = new PArray();

            Log.LogTaskName("ACTool");
            Log.LogTaskProperty("AppManifest", AppManifest);
            Log.LogTaskProperty("DeviceModel", DeviceModel);
            Log.LogTaskProperty("DeviceOSVersion", DeviceOSVersion);
            Log.LogTaskProperty("EnableOnDemandResources", EnableOnDemandResources);
            Log.LogTaskProperty("ImageAssets", ImageAssets);
            Log.LogTaskProperty("IntermediateOutputPath", IntermediateOutputPath);
            Log.LogTaskProperty("IsWatchApp", IsWatchApp);
            Log.LogTaskProperty("OptimizePNGs", OptimizePNGs);
            Log.LogTaskProperty("OutputPath", OutputPath);
            Log.LogTaskProperty("ProjectDir", ProjectDir);
            Log.LogTaskProperty("ResourcePrefix", ResourcePrefix);
            Log.LogTaskProperty("SdkBinPath", SdkBinPath);
            Log.LogTaskProperty("SdkPlatform", SdkPlatform);
            Log.LogTaskProperty("SdkVersion", SdkVersion);

            switch (SdkPlatform)
            {
            case "iPhoneSimulator":
            case "iPhoneOS":
            case "MacOSX":
            case "WatchSimulator":
            case "WatchOS":
            case "AppleTVSimulator":
            case "AppleTVOS":
                break;

            default:
                Log.LogError("Unrecognized platform: {0}", SdkPlatform);
                return(false);
            }

            if (AppManifest != null)
            {
                try {
                    plist = PDictionary.FromFile(AppManifest.ItemSpec);
                } catch (Exception ex) {
                    Log.LogError(null, null, null, AppManifest.ItemSpec, 0, 0, 0, 0, "{0}", ex.Message);
                    return(false);
                }

                bundleIdentifier = plist.GetCFBundleIdentifier();
            }

            for (int i = 0; i < ImageAssets.Length; i++)
            {
                var vpath = BundleResource.GetVirtualProjectPath(ProjectDir, ImageAssets[i], !string.IsNullOrEmpty(SessionId));

                // Ignore MacOS .DS_Store files...
                if (Path.GetFileName(vpath).Equals(".DS_Store", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
                var catalog = Path.GetDirectoryName(vpath);

                // keep walking up the directory structure until we get to the .xcassets directory
                while (!string.IsNullOrEmpty(catalog) && Path.GetExtension(catalog) != ".xcassets")
                {
                    catalog = Path.GetDirectoryName(catalog);
                }

                if (string.IsNullOrEmpty(catalog))
                {
                    Log.LogWarning(null, null, null, ImageAssets[i].ItemSpec, 0, 0, 0, 0, "Asset not part of an asset catalog: {0}", ImageAssets[i].ItemSpec);
                    continue;
                }

                if (ImageAssets[i].GetMetadata("Link") != null)
                {
                    // Note: if any of the files within a catalog are linked, we'll have to clone the *entire* catalog
                    clones.Add(catalog);
                    continue;
                }

                // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
                if (Path.GetFileName(vpath) != "Contents.json")
                {
                    continue;
                }

                items.Add(ImageAssets[i]);
            }

            // clone any *.xcassets dirs that need cloning
            if (clones.Count > 0)
            {
                if (Directory.Exists(intermediateCloneDir))
                {
                    Directory.Delete(intermediateCloneDir, true);
                }

                Directory.CreateDirectory(intermediateCloneDir);

                items.Clear();

                for (int i = 0; i < ImageAssets.Length; i++)
                {
                    var       vpath = BundleResource.GetVirtualProjectPath(ProjectDir, ImageAssets[i], !string.IsNullOrEmpty(SessionId));
                    var       clone = false;
                    ITaskItem item;

                    // Ignore MacOS .DS_Store files...
                    if (Path.GetFileName(vpath).Equals(".DS_Store", StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    foreach (var catalog in clones)
                    {
                        if (vpath.Length > catalog.Length && vpath[catalog.Length] == '/' && vpath.StartsWith(catalog, StringComparison.Ordinal))
                        {
                            clone = true;
                            break;
                        }
                    }

                    if (clone)
                    {
                        var src = ImageAssets[i].GetMetadata("FullPath");

                        if (!File.Exists(src))
                        {
                            Log.LogError(null, null, null, src, 0, 0, 0, 0, "File not found: {0}", src);
                            return(false);
                        }

                        var dest = Path.Combine(intermediateCloneDir, vpath);
                        var dir  = Path.GetDirectoryName(dest);

                        Directory.CreateDirectory(dir);

                        File.Copy(src, dest, true);

                        // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
                        if (Path.GetFileName(vpath) != "Contents.json")
                        {
                            continue;
                        }

                        item = new TaskItem(dest);
                        ImageAssets[i].CopyMetadataTo(item);
                        item.SetMetadata("Link", vpath);
                    }
                    else
                    {
                        // filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
                        if (Path.GetFileName(vpath) != "Contents.json")
                        {
                            continue;
                        }

                        item = ImageAssets[i];
                    }

                    items.Add(item);
                }
            }

            // Note: `items` contains only the Contents.json files at this point
            for (int i = 0; i < items.Count; i++)
            {
                var vpath = BundleResource.GetVirtualProjectPath(ProjectDir, items[i], !string.IsNullOrEmpty(SessionId));
                var path  = items[i].GetMetadata("FullPath");

                // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
                var catalog = Path.GetDirectoryName(vpath);
                path = Path.GetDirectoryName(path);

                // keep walking up the directory structure until we get to the .xcassets directory
                while (!string.IsNullOrEmpty(catalog) && Path.GetExtension(catalog) != ".xcassets")
                {
                    catalog = Path.GetDirectoryName(catalog);
                    path    = Path.GetDirectoryName(path);
                }

                if (unique.Add(catalog))
                {
                    var item = new TaskItem(path);
                    item.SetMetadata("Link", catalog);

                    catalogs.Add(item);
                }

                if (AppleSdkSettings.XcodeVersion.Major >= 7 && !string.IsNullOrEmpty(bundleIdentifier) && SdkPlatform != "WatchSimulator")
                {
                    var text = File.ReadAllText(items[i].ItemSpec);

                    if (string.IsNullOrEmpty(text))
                    {
                        continue;
                    }

                    var json = JsonConvert.DeserializeObject(text) as JObject;

                    if (json == null)
                    {
                        continue;
                    }

                    var properties = json.Property("properties");

                    if (properties == null)
                    {
                        continue;
                    }

                    var resourceTags = properties.Value.ToObject <JObject> ().Property("on-demand-resource-tags");

                    if (resourceTags == null || resourceTags.Value.Type != JTokenType.Array)
                    {
                        continue;
                    }

                    var    tagArray = resourceTags.Value.ToObject <JArray> ();
                    var    tags     = new HashSet <string> ();
                    string hash;

                    foreach (var tag in tagArray.Select(token => token.ToObject <string> ()))
                    {
                        tags.Add(tag);
                    }

                    var tagList = tags.ToList();
                    tagList.Sort();

                    var assetDir = AssetPackUtils.GetAssetPackDirectory(intermediate, bundleIdentifier, tagList, out hash);

                    if (knownSpecs.Add(hash))
                    {
                        var assetpack = new PDictionary();
                        var ptags     = new PArray();

                        Directory.CreateDirectory(assetDir);

                        for (int j = 0; j < tagList.Count; j++)
                        {
                            ptags.Add(new PString(tagList[j]));
                        }

                        assetpack.Add("bundle-id", new PString(string.Format("{0}.asset-pack-{1}", bundleIdentifier, hash)));
                        assetpack.Add("bundle-path", new PString(Path.GetFullPath(assetDir)));
                        assetpack.Add("tags", ptags);
                        specs.Add(assetpack);
                    }
                }
            }

            if (catalogs.Count == 0)
            {
                // There are no (supported?) asset catalogs
                return(true);
            }

            partialAppManifest = new TaskItem(Path.Combine(intermediate, "partial-info.plist"));

            if (specs.Count > 0)
            {
                outputSpecs = Path.Combine(intermediate, "output-specifications.plist");
                specs.Save(outputSpecs, true);
            }

            Directory.CreateDirectory(intermediateBundleDir);

            // Note: Compile() will set the PartialAppManifest property if it is used...
            if ((Compile(catalogs.ToArray(), intermediateBundleDir, manifest)) != 0)
            {
                return(false);
            }

            if (PartialAppManifest != null && !File.Exists(PartialAppManifest.GetMetadata("FullPath")))
            {
                Log.LogError("Partial Info.plist file was not generated: {0}", PartialAppManifest.GetMetadata("FullPath"));
            }

            try {
                var manifestOutput = PDictionary.FromFile(manifest.ItemSpec);

                LogWarningsAndErrors(manifestOutput, catalogs[0]);

                bundleResources.AddRange(GetCompiledBundleResources(manifestOutput, intermediateBundleDir));
                outputManifests.Add(manifest);
            } catch (Exception ex) {
                Log.LogError("Failed to load output manifest for {0} for the file {2}: {1}", ToolName, ex.Message, manifest.ItemSpec);
            }

            foreach (var assetpack in specs.OfType <PDictionary> ())
            {
                var path       = Path.Combine(assetpack.GetString("bundle-path").Value, "Info.plist");
                var bundlePath = PathUtils.AbsoluteToRelative(intermediate, path);
                var outputPath = Path.Combine(OutputPath, bundlePath);
                var rpath      = Path.Combine(intermediate, bundlePath);
                var dict       = new PDictionary();

                dict.SetCFBundleIdentifier(assetpack.GetString("bundle-id").Value);
                dict.Add("Tags", assetpack.GetArray("tags").Clone());

                dict.Save(path, true, true);

                var item = new TaskItem(rpath);
                item.SetMetadata("LogicalName", bundlePath);
                item.SetMetadata("OutputPath", outputPath);
                item.SetMetadata("Optimize", "false");

                bundleResources.Add(item);
            }

            BundleResources = bundleResources.ToArray();
            OutputManifests = outputManifests.ToArray();

            return(!Log.HasLoggedErrors);
        }
예제 #25
0
        public override bool Execute()
        {
            Log.LogTaskName ("ComputeBundleResourceOutputPaths");
            Log.LogTaskProperty ("AppBundleDir", AppBundleDir);
            Log.LogTaskProperty ("BundleIdentifier", BundleIdentifier);
            Log.LogTaskProperty ("BundleResources", BundleResources);
            Log.LogTaskProperty ("IntermediateOutputPath", IntermediateOutputPath);
            Log.LogTaskProperty ("OutputPath", OutputPath);

            var intermediate = Path.Combine (IntermediateOutputPath, "assetpacks");
            var bundleResources = new List<ITaskItem> ();
            var packs = new HashSet<string> ();

            if (BundleResources != null) {
                foreach (var item in BundleResources) {
                    var logicalName = item.GetMetadata ("LogicalName");
                    var outputPath = item.GetMetadata ("OutputPath");
                    var tags = AssetPackUtils.GetResourceTags (item);
                    string hash;

                    if (tags != null) {
                        var assetpack = AssetPackUtils.GetAssetPackDirectory (OutputPath, BundleIdentifier, tags, out hash);

                        if (packs.Add (hash)) {
                            var path = Path.Combine (intermediate, hash + ".plist");

                            if (!File.Exists (path)) {
                                var plist = new PDictionary ();
                                var array = new PArray ();

                                for (int i = 0; i < tags.Count; i++)
                                    array.Add (new PString (tags[i]));

                                plist.SetCFBundleIdentifier (BundleIdentifier + ".asset-pack-" + hash);
                                plist.Add ("Tags", array);

                                Directory.CreateDirectory (intermediate);

                                plist.Save (path, true, true);
                            }

                            var manifest = new TaskItem (path);
                            manifest.SetMetadata ("OutputPath", Path.Combine (assetpack, "Info.plist"));
                            bundleResources.Add (manifest);
                        }

                        outputPath = Path.Combine (assetpack, logicalName);
                    } else if (string.IsNullOrEmpty (outputPath)) {
                        outputPath = Path.Combine (AppBundleDir.ItemSpec, logicalName);
                    }

                    var bundleResource = new TaskItem (item);

                    bundleResource.SetMetadata ("OutputPath", outputPath);

                    bundleResources.Add (bundleResource);
                }
            }

            BundleResourcesWithOutputPaths = bundleResources.ToArray ();

            return !Log.HasLoggedErrors;
        }
        bool Compile(PDictionary plist)
        {
            var currentSDK = IPhoneSdks.GetSdk(Framework);

            if (!currentSDK.SdkIsInstalled(sdkVersion, SdkIsSimulator))
            {
                Log.LogError(null, null, null, null, 0, 0, 0, 0, "The {0} SDK for '{1}' is not installed.", Framework, sdkVersion);
                return(false);
            }

            supportedDevices = plist.GetUIDeviceFamily();

            if (!IsWatchApp)
            {
                var version = IPhoneSdks.MonoTouch.ExtendedVersion;
                // This key is our supported way of determining if an app
                // was built with Xamarin, so it needs to be present in all apps.

                var dict = new PDictionary();
                dict.Add("Version", new PString(string.Format("{0} ({1}: {2})", version.Version, version.Branch, version.Hash)));
                plist.Add("com.xamarin.ios", dict);
            }

            var sdkSettings = currentSDK.GetSdkSettings(sdkVersion, SdkIsSimulator);
            var dtSettings  = currentSDK.GetDTSettings();

            SetValue(plist, ManifestKeys.BuildMachineOSBuild, dtSettings.BuildMachineOSBuild);
            // We have an issue here, this is for consideration by the platform:
            // CFLocaleCopyCurrent(), used in the mono code to get the current locale (locale.c line 421), will return the value of the application's CFBundleDevelopmentRegion Info.plist key if all of the following conditions are true:
            //
            // * CFBundleDevelopmentRegion is present in the Info.plist
            // * The CFBundleDevelopmentRegion language is in the list of preferred languages on the iOS device, but isn't the first one
            // * There are no localized resources (i.e. no .lproj directory) in the app for the first preferred locale
            //
            // This differs from iOS 10 where the presence of the CFBundleDevelopmentRegion key had no effect. Commenting this line out, ensures that CurrentCulture is correct and behaves like the iOS 10 version.
            // plist.SetIfNotPresent (ManifestKeys.CFBundleDevelopmentRegion, "en");

            plist.SetIfNotPresent(ManifestKeys.CFBundleExecutable, AssemblyName);
            if (IsIOS)
            {
                var executable = plist.GetCFBundleExecutable();
                if (executable.EndsWith(".app", StringComparison.Ordinal))
                {
                    LogAppManifestError("The executable (CFBundleExecutable) name ({0}) cannot end with '.app', because iOS may fail to launch the app.", executable);
                }
            }

            if (IsIOS)
            {
                if (minimumOSVersion < IPhoneSdkVersion.V5_0 && plist.GetUIMainStoryboardFile(true) != null)
                {
                    LogAppManifestError("Applications using a storyboard as the Main Interface must have a deployment target greater than 5.0");
                }

                if (!plist.ContainsKey(ManifestKeys.CFBundleName))
                {
                    plist [ManifestKeys.CFBundleName] = plist.ContainsKey(ManifestKeys.CFBundleDisplayName) ? plist.GetString(ManifestKeys.CFBundleDisplayName).Clone() : new PString(AppBundleName);
                }
            }
            else
            {
                plist.SetIfNotPresent(ManifestKeys.CFBundleName, AppBundleName);
            }

            plist.SetIfNotPresent(ManifestKeys.CFBundleIdentifier, BundleIdentifier);
            plist.SetIfNotPresent(ManifestKeys.CFBundleInfoDictionaryVersion, "6.0");
            plist.SetIfNotPresent(ManifestKeys.CFBundlePackageType, IsAppExtension ? "XPC!" : "APPL");
            if (!string.IsNullOrEmpty(ResourceRules))
            {
                plist.SetIfNotPresent(ManifestKeys.CFBundleResourceSpecification, Path.GetFileName(ResourceRules));
            }
            plist.SetIfNotPresent(ManifestKeys.CFBundleSignature, "????");
            if (!plist.ContainsKey(ManifestKeys.CFBundleSupportedPlatforms))
            {
                plist[ManifestKeys.CFBundleSupportedPlatforms] = new PArray {
                    SdkPlatform
                }
            }
            ;
            plist.SetIfNotPresent(ManifestKeys.CFBundleVersion, "1.0");
            plist.SetIfNotPresent(ManifestKeys.CFBundleShortVersionString, plist.GetCFBundleVersion());

            string dtCompiler        = null;
            string dtPlatformBuild   = null;
            string dtSDKBuild        = null;
            string dtPlatformName    = null;
            string dtPlatformVersion = null;
            string dtXcode           = null;
            string dtXcodeBuild      = null;

            if (!SdkIsSimulator)
            {
                dtCompiler      = sdkSettings.DTCompiler;
                dtPlatformBuild = dtSettings.DTPlatformBuild;
                dtSDKBuild      = sdkSettings.DTSDKBuild;
            }

            dtPlatformName = SdkPlatform.ToLowerInvariant();
            if (!SdkIsSimulator)
            {
                dtPlatformVersion = dtSettings.DTPlatformVersion;
            }

            var dtSDKName = sdkSettings.CanonicalName;

            // older sdksettings didn't have a canonicalname for sim
            if (SdkIsSimulator && string.IsNullOrEmpty(dtSDKName))
            {
                var deviceSdkSettings = currentSDK.GetSdkSettings(sdkVersion, false);
                dtSDKName = deviceSdkSettings.AlternateSDK;
            }

            if (!SdkIsSimulator)
            {
                dtXcode      = AppleSdkSettings.DTXcode;
                dtXcodeBuild = dtSettings.DTXcodeBuild;
            }

            if (UseFakeWatchOS4_3Sdk)
            {
                // This is a workaround for https://github.com/xamarin/xamarin-macios/issues/4810
                if (Framework == PlatformFramework.WatchOS)
                {
                    if (dtPlatformBuild != null)
                    {
                        dtPlatformBuild = "15T212";
                    }
                    if (dtPlatformVersion != null)
                    {
                        dtPlatformVersion = "4.3";
                    }
                    if (dtSDKBuild != null)
                    {
                        dtSDKBuild = "15T212";
                    }
                    if (dtSDKName != null)
                    {
                        dtSDKName = "watchos4.3";
                    }
                    if (dtXcode != null)
                    {
                        dtXcode = "0940";
                    }
                    if (dtXcodeBuild != null)
                    {
                        dtXcodeBuild = "9F1027a";
                    }
                }
                else
                {
                    Log.LogWarning("Can only fake the watchOS 4.3 SDK when building for watchOS.");
                }
            }

            SetValueIfNotNull(plist, "DTCompiler", dtCompiler);
            SetValueIfNotNull(plist, "DTPlatformBuild", dtPlatformBuild);
            SetValueIfNotNull(plist, "DTSDKBuild", dtSDKBuild);
            plist.SetIfNotPresent("DTPlatformName", dtPlatformName);
            SetValueIfNotNull(plist, "DTPlatformVersion", dtPlatformVersion);
            SetValue(plist, "DTSDKName", dtSDKName);
            SetValueIfNotNull(plist, "DTXcode", dtXcode);
            SetValueIfNotNull(plist, "DTXcodeBuild", dtXcodeBuild);

            SetDeviceFamily(plist);

            plist.SetIfNotPresent(ManifestKeys.MinimumOSVersion, minimumOSVersion.ToString());

            if (IsWatchExtension)
            {
                // Note: Only watchOS1 Extensions target Xamarin.iOS
                if (Framework == PlatformFramework.iOS)
                {
                    PObject value;

                    if (!plist.TryGetValue(ManifestKeys.UIRequiredDeviceCapabilities, out value))
                    {
                        var capabilities = new PArray();
                        capabilities.Add(new PString("watch-companion"));

                        plist.Add(ManifestKeys.UIRequiredDeviceCapabilities, capabilities);
                    }
                    else if (value is PDictionary)
                    {
                        var capabilities = (PDictionary)value;

                        if (!capabilities.ContainsKey("watch-companion"))
                        {
                            capabilities.Add("watch-companion", new PBoolean(true));
                        }
                    }
                    else
                    {
                        var  capabilities = (PArray)value;
                        bool exists       = false;

                        foreach (var capability in capabilities.OfType <PString> ())
                        {
                            if (capability.Value != "watch-companion")
                            {
                                continue;
                            }

                            exists = true;
                            break;
                        }

                        if (!exists)
                        {
                            capabilities.Add(new PString("watch-companion"));
                        }
                    }
                }

                if (Debug)
                {
                    SetAppTransportSecurity(plist);
                }
            }

            // Remove any Xamarin Studio specific keys
            plist.Remove(ManifestKeys.XSLaunchImageAssets);
            plist.Remove(ManifestKeys.XSAppIconAssets);

            // Merge with any partial plists generated by the Asset Catalog compiler...
            MergePartialPlistTemplates(plist);

            SetRequiredArchitectures(plist);

            if (IsIOS)
            {
                Validation(plist);
            }

            CompiledAppManifest = new TaskItem(Path.Combine(AppBundleDir, "Info.plist"));
            plist.Save(CompiledAppManifest.ItemSpec, true, true);

            return(!Log.HasLoggedErrors);
        }

        void SetValueIfNotNull(PDictionary dict, string key, string value)
        {
            if (value == null)
            {
                return;
            }
            SetValue(dict, key, value);
        }

        void SetRequiredArchitectures(PDictionary plist)
        {
            PObject capabilities;

            if (plist.TryGetValue(ManifestKeys.UIRequiredDeviceCapabilities, out capabilities))
            {
                if (capabilities is PArray)
                {
                    var architectureValues = new HashSet <string> (new[] { "armv6", "armv7", "arm64" });
                    var array = (PArray)capabilities;

                    // Remove any architecture values
                    for (int i = 0; i < array.Count; i++)
                    {
                        var value = array[i] as PString;

                        if (value == null || !architectureValues.Contains(value.Value))
                        {
                            continue;
                        }

                        array.RemoveAt(i);
                    }

                    // If-and-only-if the TargetArchitecture is a single architecture, set it as a required device capability
                    switch (architectures)
                    {
                    case TargetArchitecture.ARM64:
                        array.Add(new PString("arm64"));
                        break;

                    case TargetArchitecture.ARMv7:
                        array.Add(new PString("armv7"));
                        break;
                    }
                }
                else if (capabilities is PDictionary)
                {
                    var dict = (PDictionary)capabilities;

                    switch (architectures)
                    {
                    case TargetArchitecture.ARM64:
                        dict["arm64"] = new PBoolean(true);
                        dict.Remove("armv6");
                        dict.Remove("armv7");
                        break;

                    case TargetArchitecture.ARMv7:
                        dict["armv7"] = new PBoolean(true);
                        dict.Remove("armv6");
                        dict.Remove("arm64");
                        break;

                    default:
                        dict.Remove("armv6");
                        dict.Remove("armv7");
                        dict.Remove("arm64");
                        break;
                    }
                }
            }
            else
            {
                var array = new PArray();

                // If-and-only-if the TargetArchitecture is a single architecture, set it as a required device capability
                switch (architectures)
                {
                case TargetArchitecture.ARM64:
                    array.Add(new PString("arm64"));
                    break;

                case TargetArchitecture.ARMv7:
                    array.Add(new PString("armv7"));
                    break;
                }

                if (array.Count > 0)
                {
                    plist.Add(ManifestKeys.UIRequiredDeviceCapabilities, array);
                }
            }
        }

        void SetDeviceFamily(PDictionary plist)
        {
            switch (Framework)
            {
            case PlatformFramework.iOS:
                SetIOSDeviceFamily(plist);
                break;

            case PlatformFramework.WatchOS:
                plist.SetUIDeviceFamily(IPhoneDeviceType.Watch);
                break;

            case PlatformFramework.TVOS:
                plist.SetUIDeviceFamily(IPhoneDeviceType.TV);
                break;
            }
        }

        void SetIOSDeviceFamily(PDictionary plist)
        {
            if (IsWatchApp)
            {
                if (SdkIsSimulator)
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.IPhone | IPhoneDeviceType.Watch);
                }
                else
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.Watch);
                }
            }
            else
            {
                if (!IsAppExtension)
                {
                    plist.SetIfNotPresent(ManifestKeys.LSRequiresIPhoneOS, true);
                }

                if (minimumOSVersion >= IPhoneSdkVersion.V3_2 && supportedDevices == IPhoneDeviceType.NotSet)
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.IPhone);
                }
            }
        }

        void SetAppTransportSecurity(PDictionary plist)
        {
            // Debugging over http has a couple of gotchas:
            // * We can't use https, because that requires a valid server certificate,
            //   which we can't ensure.
            //   It would also require a hostname for the mac, which it might not have either.
            // * NSAppTransportSecurity/NSExceptionDomains does not allow exceptions based
            //   on IP address (only hostname).
            // * Which means the only way to make sure watchOS allows connections from
            //   the app on device to the mac is to disable App Transport Security altogether.
            // Good news: watchOS 3 will apparently not apply ATS when connecting
            // directly to IP addresses, which means we won't have to do this at all
            // (sometime in the future).

            PDictionary ats;

            if (!plist.TryGetValue(ManifestKeys.NSAppTransportSecurity, out ats))
            {
                plist.Add(ManifestKeys.NSAppTransportSecurity, ats = new PDictionary());
            }

            if (ats.GetBoolean(ManifestKeys.NSAllowsArbitraryLoads))
            {
                Log.LogMessage(MessageImportance.Low, "All http loads are already allowed.");
            }
            else
            {
                Log.LogMessage(MessageImportance.Low, "Allowed arbitrary HTTP loads to support debugging.");
                ats.SetBooleanOrRemove(ManifestKeys.NSAllowsArbitraryLoads, true);
            }
        }

        void Validation(PDictionary plist)
        {
            var supportsIPhone = (supportedDevices & IPhoneDeviceType.IPhone) != 0 ||
                                 supportedDevices == IPhoneDeviceType.NotSet;
            var supportsIPad = (supportedDevices & IPhoneDeviceType.IPad) != 0;

            // Validation...
            if (!IsAppExtension && sdkVersion >= IPhoneSdkVersion.V3_2)
            {
                IPhoneOrientation orientation;

                if (supportsIPhone)
                {
                    orientation = plist.GetUISupportedInterfaceOrientations(false);
                    if (orientation == IPhoneOrientation.None)
                    {
                        LogAppManifestWarning("Supported iPhone orientations have not been set");
                    }
                    else if (!orientation.IsValidPair())
                    {
                        LogAppManifestWarning("Supported iPhone orientations are not matched pairs");
                    }
                }

                if (supportsIPad)
                {
                    orientation = plist.GetUISupportedInterfaceOrientations(true);
                    if (orientation == IPhoneOrientation.None)
                    {
                        LogAppManifestWarning("Supported iPad orientations have not been set");
                    }
                    else if (!orientation.IsValidPair())
                    {
                        LogAppManifestWarning("Supported iPad orientations are not matched pairs");
                    }
                }
            }
        }
    }
예제 #27
0
        void AddIconPaths(PArray icons, PArray iconFiles, string productsDir)
        {
            foreach (var icon in iconFiles.Cast<PString> ().Where (p => p.Value != null)) {
                var path = string.Format ("Applications/{0}/{1}", Path.GetFileName (AppBundleDir.ItemSpec), icon.Value);
                bool addDefault = true;

                if (path.EndsWith (".png", StringComparison.Ordinal)) {
                    icons.Add (new PString (path));
                    continue;
                }

                if (File.Exists (Path.Combine (productsDir, path + "@3x.png"))) {
                    icons.Add (new PString (path + "@3x.png"));
                    addDefault = false;
                }

                if (File.Exists (Path.Combine (productsDir, path + "@2x.png"))) {
                    icons.Add (new PString (path + "@2x.png"));
                    addDefault = false;
                }

                if (addDefault || File.Exists (Path.Combine (productsDir, path + ".png")))
                    icons.Add (new PString (path + ".png"));
            }
        }
예제 #28
0
        public override bool Execute()
        {
            var    intermediate          = Path.Combine(IntermediateOutputPath, ToolName);
            var    intermediateBundleDir = Path.Combine(intermediate, "bundle");
            var    manifest         = new TaskItem(Path.Combine(intermediate, "asset-manifest.plist"));
            var    bundleResources  = new List <ITaskItem> ();
            var    outputManifests  = new List <ITaskItem> ();
            var    catalogs         = new List <ITaskItem> ();
            var    unique           = new HashSet <string> ();
            string bundleIdentifier = null;
            var    knownSpecs       = new HashSet <string> ();
            var    specs            = new PArray();
            int    rc;

            Log.LogTaskName("ACTool");
            Log.LogTaskProperty("AppManifest", AppManifest);
            Log.LogTaskProperty("DeviceModel", DeviceModel);
            Log.LogTaskProperty("DeviceOSVersion", DeviceOSVersion);
            Log.LogTaskProperty("ImageAssets", ImageAssets);
            Log.LogTaskProperty("IntermediateOutputPath", IntermediateOutputPath);
            Log.LogTaskProperty("IsWatchApp", IsWatchApp);
            Log.LogTaskProperty("OptimizePNGs", OptimizePNGs);
            Log.LogTaskProperty("OutputPath", OutputPath);
            Log.LogTaskProperty("ProjectDir", ProjectDir);
            Log.LogTaskProperty("ResourcePrefix", ResourcePrefix);
            Log.LogTaskProperty("SdkBinPath", SdkBinPath);
            Log.LogTaskProperty("SdkPlatform", SdkPlatform);
            Log.LogTaskProperty("SdkVersion", SdkVersion);

            switch (SdkPlatform)
            {
            case "iPhoneSimulator":
            case "iPhoneOS":
            case "MacOSX":
            case "WatchSimulator":
            case "WatchOS":
            case "AppleTVSimulator":
            case "AppleTVOS":
                break;

            default:
                Log.LogError("Unrecognized platform: {0}", SdkPlatform);
                return(false);
            }

            if (AppManifest != null)
            {
                try {
                    plist = PDictionary.FromFile(AppManifest.ItemSpec);
                } catch (Exception ex) {
                    Log.LogError(null, null, null, AppManifest.ItemSpec, 0, 0, 0, 0, "{0}", ex.Message);
                    return(false);
                }

                bundleIdentifier = plist.GetCFBundleIdentifier();
            }

            foreach (var asset in ImageAssets)
            {
                var vpath = BundleResource.GetVirtualProjectPath(ProjectDir, asset);
                if (Path.GetFileName(vpath) != "Contents.json")
                {
                    continue;
                }

                // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
                var catalog = Path.GetDirectoryName(vpath);

                // keep walking up the directory structure until we get to the .xcassets directory
                while (!string.IsNullOrEmpty(catalog) && Path.GetExtension(catalog) != ".xcassets")
                {
                    catalog = Path.GetDirectoryName(catalog);
                }

                if (string.IsNullOrEmpty(catalog))
                {
                    Log.LogWarning(null, null, null, asset.ItemSpec, 0, 0, 0, 0, "Asset not part of an asset catalog: {0}", asset.ItemSpec);
                    continue;
                }

                if (unique.Add(catalog))
                {
                    catalogs.Add(new TaskItem(catalog));
                }

                if (AppleSdkSettings.XcodeVersion.Major >= 7 && !string.IsNullOrEmpty(bundleIdentifier) && SdkPlatform != "WatchSimulator")
                {
                    var text = File.ReadAllText(asset.ItemSpec);

                    if (string.IsNullOrEmpty(text))
                    {
                        continue;
                    }

                    var json = JsonConvert.DeserializeObject(text) as JObject;

                    if (json == null)
                    {
                        continue;
                    }

                    var properties = json.Property("properties");

                    if (properties == null)
                    {
                        continue;
                    }

                    var resourceTags = properties.Value.ToObject <JObject> ().Property("on-demand-resource-tags");

                    if (resourceTags == null || resourceTags.Value.Type != JTokenType.Array)
                    {
                        continue;
                    }

                    var    tagArray = resourceTags.Value.ToObject <JArray> ();
                    var    tags     = new HashSet <string> ();
                    string hash;

                    foreach (var tag in tagArray.Select(token => token.ToObject <string> ()))
                    {
                        tags.Add(tag);
                    }

                    var tagList = tags.ToList();
                    tagList.Sort();

                    var path = AssetPackUtils.GetAssetPackDirectory(intermediate, bundleIdentifier, tagList, out hash);

                    if (knownSpecs.Add(hash))
                    {
                        var assetpack = new PDictionary();
                        var ptags     = new PArray();

                        Directory.CreateDirectory(path);

                        for (int i = 0; i < tags.Count; i++)
                        {
                            ptags.Add(new PString(tagList[i]));
                        }

                        assetpack.Add("bundle-id", new PString(string.Format("{0}.asset-pack-{1}", bundleIdentifier, hash)));
                        assetpack.Add("bundle-path", new PString(Path.GetFullPath(path)));
                        assetpack.Add("tags", ptags);
                        specs.Add(assetpack);
                    }
                }
            }

            if (catalogs.Count == 0)
            {
                // There are no (supported?) asset catalogs
                return(true);
            }

            partialAppManifest = new TaskItem(Path.Combine(intermediate, "partial-info.plist"));

            if (specs.Count > 0)
            {
                outputSpecs = Path.Combine(intermediate, "output-specifications.plist");
                specs.Save(outputSpecs, true);
            }

            var output = new TaskItem(intermediateBundleDir);

            Directory.CreateDirectory(intermediateBundleDir);

            // Note: Compile() will set the PartialAppManifest property if it is used...
            if ((rc = Compile(catalogs.ToArray(), output, manifest)) != 0)
            {
                if (File.Exists(manifest.ItemSpec))
                {
                    try {
                        var log = PDictionary.FromFile(manifest.ItemSpec);

                        LogWarningsAndErrors(log, catalogs[0]);
                    } catch (FormatException) {
                        Log.LogError("actool exited with code {0}", rc);
                    }

                    File.Delete(manifest.ItemSpec);
                }

                return(false);
            }

            if (PartialAppManifest != null && !File.Exists(PartialAppManifest.GetMetadata("FullPath")))
            {
                Log.LogError("Partial Info.plist file was not generated: {0}", PartialAppManifest.GetMetadata("FullPath"));
            }

            try {
                var manifestOutput = PDictionary.FromFile(manifest.ItemSpec);

                LogWarningsAndErrors(manifestOutput, catalogs[0]);

                bundleResources.AddRange(GetCompiledBundleResources(manifestOutput, intermediateBundleDir));
                outputManifests.Add(manifest);
            } catch (Exception ex) {
                Log.LogError("Failed to load output manifest for {0} for the file {2}: {1}", ToolName, ex.Message, manifest.ItemSpec);
            }

            foreach (var assetpack in specs.OfType <PDictionary> ())
            {
                var path       = Path.Combine(assetpack.GetString("bundle-path").Value, "Info.plist");
                var bundlePath = PathUtils.AbsoluteToRelative(intermediate, path);
                var outputPath = Path.Combine(OutputPath, bundlePath);
                var rpath      = Path.Combine(intermediate, bundlePath);
                var dict       = new PDictionary();

                dict.SetCFBundleIdentifier(assetpack.GetString("bundle-id").Value);
                dict.Add("Tags", assetpack.GetArray("tags").Clone());

                dict.Save(path, true, true);

                var item = new TaskItem(rpath);
                item.SetMetadata("LogicalName", bundlePath);
                item.SetMetadata("OutputPath", outputPath);
                item.SetMetadata("Optimize", "false");

                bundleResources.Add(item);
            }

            BundleResources = bundleResources.ToArray();
            OutputManifests = outputManifests.ToArray();

            return(!Log.HasLoggedErrors);
        }
        public override bool Execute()
        {
            var intermediate    = Path.Combine(IntermediateOutputPath, "assetpacks");
            var bundleResources = new List <ITaskItem> ();
            var packs           = new HashSet <string> ();

            if (BundleResources != null)
            {
                foreach (var item in BundleResources)
                {
                    var            logicalName = item.GetMetadata("LogicalName");
                    var            outputPath  = item.GetMetadata("OutputPath");
                    IList <string> tags;
                    string         hash;

                    if (EnableOnDemandResources && (tags = AssetPackUtils.GetResourceTags(item)) != null)
                    {
                        var assetpack = AssetPackUtils.GetAssetPackDirectory(OutputPath, BundleIdentifier, tags, out hash);

                        if (packs.Add(hash))
                        {
                            var path = Path.Combine(intermediate, hash + ".plist");

                            if (!File.Exists(path))
                            {
                                var plist = new PDictionary();
                                var array = new PArray();

                                for (int i = 0; i < tags.Count; i++)
                                {
                                    array.Add(new PString(tags[i]));
                                }

                                plist.SetCFBundleIdentifier(BundleIdentifier + ".asset-pack-" + hash);
                                plist.Add("Tags", array);

                                Directory.CreateDirectory(intermediate);

                                plist.Save(path, true, true);
                            }

                            var manifest = new TaskItem(path);
                            manifest.SetMetadata("OutputPath", Path.Combine(assetpack, "Info.plist"));
                            bundleResources.Add(manifest);
                        }

                        outputPath = Path.Combine(assetpack, logicalName);
                    }
                    else if (string.IsNullOrEmpty(outputPath))
                    {
                        outputPath = Path.Combine(AppBundleDir.ItemSpec, logicalName);
                    }

                    var bundleResource = new TaskItem(item);

                    bundleResource.SetMetadata("OutputPath", outputPath);

                    bundleResources.Add(bundleResource);
                }
            }

            BundleResourcesWithOutputPaths = bundleResources.ToArray();

            return(!Log.HasLoggedErrors);
        }
예제 #30
0
        public override bool Execute()
        {
            var intermediate = Path.Combine (IntermediateOutputPath, ToolName);
            var intermediateBundleDir = Path.Combine (intermediate, "bundle");
            var manifest = new TaskItem (Path.Combine (intermediate, "asset-manifest.plist"));
            var bundleResources = new List<ITaskItem> ();
            var outputManifests = new List<ITaskItem> ();
            var catalogs = new List<ITaskItem> ();
            var unique = new HashSet<string> ();
            string bundleIdentifier = null;
            var knownSpecs = new HashSet<string> ();
            var specs = new PArray ();
            int rc;

            Log.LogTaskName ("ACTool");
            Log.LogTaskProperty ("AppManifest", AppManifest);
            Log.LogTaskProperty ("DeviceModel", DeviceModel);
            Log.LogTaskProperty ("DeviceOSVersion", DeviceOSVersion);
            Log.LogTaskProperty ("ImageAssets", ImageAssets);
            Log.LogTaskProperty ("IntermediateOutputPath", IntermediateOutputPath);
            Log.LogTaskProperty ("IsWatchApp", IsWatchApp);
            Log.LogTaskProperty ("OptimizePNGs", OptimizePNGs);
            Log.LogTaskProperty ("OutputPath", OutputPath);
            Log.LogTaskProperty ("ProjectDir", ProjectDir);
            Log.LogTaskProperty ("ResourcePrefix", ResourcePrefix);
            Log.LogTaskProperty ("SdkBinPath", SdkBinPath);
            Log.LogTaskProperty ("SdkPlatform", SdkPlatform);
            Log.LogTaskProperty ("SdkVersion", SdkVersion);

            switch (SdkPlatform) {
            case "iPhoneSimulator":
            case "iPhoneOS":
            case "MacOSX":
            case "WatchSimulator":
            case "WatchOS":
            case "AppleTVSimulator":
            case "AppleTVOS":
                break;
            default:
                Log.LogError ("Unrecognized platform: {0}", SdkPlatform);
                return false;
            }

            if (AppManifest != null) {
                try {
                    plist = PDictionary.FromFile (AppManifest.ItemSpec);
                } catch (Exception ex) {
                    Log.LogError (null, null, null, AppManifest.ItemSpec, 0, 0, 0, 0, "{0}", ex.Message);
                    return false;
                }

                bundleIdentifier = plist.GetCFBundleIdentifier ();
            }

            foreach (var asset in ImageAssets) {
                var vpath = BundleResource.GetVirtualProjectPath (ProjectDir, asset);
                if (Path.GetFileName (vpath) != "Contents.json")
                    continue;

                // get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
                var catalog = Path.GetDirectoryName (vpath);

                // keep walking up the directory structure until we get to the .xcassets directory
                while (!string.IsNullOrEmpty (catalog) && Path.GetExtension (catalog) != ".xcassets")
                    catalog = Path.GetDirectoryName (catalog);

                if (string.IsNullOrEmpty (catalog)) {
                    Log.LogWarning (null, null, null, asset.ItemSpec, 0, 0, 0, 0, "Asset not part of an asset catalog: {0}", asset.ItemSpec);
                    continue;
                }

                if (unique.Add (catalog))
                    catalogs.Add (new TaskItem (catalog));

                if (AppleSdkSettings.XcodeVersion.Major >= 7 && !string.IsNullOrEmpty (bundleIdentifier) && SdkPlatform != "WatchSimulator") {
                    var text = File.ReadAllText (asset.ItemSpec);

                    if (string.IsNullOrEmpty (text))
                        continue;

                    var json = JsonConvert.DeserializeObject (text) as JObject;

                    if (json == null)
                        continue;

                    var properties = json.Property ("properties");

                    if (properties == null)
                        continue;

                    var resourceTags = properties.Value.ToObject<JObject> ().Property ("on-demand-resource-tags");

                    if (resourceTags == null || resourceTags.Value.Type != JTokenType.Array)
                        continue;

                    var tagArray = resourceTags.Value.ToObject<JArray> ();
                    var tags = new HashSet<string> ();
                    string hash;

                    foreach (var tag in tagArray.Select (token => token.ToObject<string> ()))
                        tags.Add (tag);

                    var tagList = tags.ToList ();
                    tagList.Sort ();

                    var path = AssetPackUtils.GetAssetPackDirectory (intermediate, bundleIdentifier, tagList, out hash);

                    if (knownSpecs.Add (hash)) {
                        var assetpack = new PDictionary ();
                        var ptags = new PArray ();

                        Directory.CreateDirectory (path);

                        for (int i = 0; i < tags.Count; i++)
                            ptags.Add (new PString (tagList[i]));

                        assetpack.Add ("bundle-id", new PString (string.Format ("{0}.asset-pack-{1}", bundleIdentifier, hash)));
                        assetpack.Add ("bundle-path", new PString (Path.GetFullPath (path)));
                        assetpack.Add ("tags", ptags);
                        specs.Add (assetpack);
                    }
                }
            }

            if (catalogs.Count == 0) {
                // There are no (supported?) asset catalogs
                return true;
            }

            partialAppManifest = new TaskItem (Path.Combine (intermediate, "partial-info.plist"));

            if (specs.Count > 0) {
                outputSpecs = Path.Combine (intermediate, "output-specifications.plist");
                specs.Save (outputSpecs, true);
            }

            var output = new TaskItem (intermediateBundleDir);

            Directory.CreateDirectory (intermediateBundleDir);

            // Note: Compile() will set the PartialAppManifest property if it is used...
            if ((rc = Compile (catalogs.ToArray (), output, manifest)) != 0) {
                if (File.Exists (manifest.ItemSpec)) {
                    try {
                        var log = PDictionary.FromFile (manifest.ItemSpec);

                        LogWarningsAndErrors (log, catalogs[0]);
                    } catch (FormatException) {
                        Log.LogError ("actool exited with code {0}", rc);
                    }

                    File.Delete (manifest.ItemSpec);
                }

                return false;
            }

            if (PartialAppManifest != null && !File.Exists (PartialAppManifest.GetMetadata ("FullPath")))
                Log.LogError ("Partial Info.plist file was not generated: {0}", PartialAppManifest.GetMetadata ("FullPath"));

            try {
                var manifestOutput = PDictionary.FromFile (manifest.ItemSpec);

                LogWarningsAndErrors (manifestOutput, catalogs[0]);

                bundleResources.AddRange (GetCompiledBundleResources (manifestOutput, intermediateBundleDir));
                outputManifests.Add (manifest);
            } catch (Exception ex) {
                Log.LogError ("Failed to load output manifest for {0} for the file {2}: {1}", ToolName, ex.Message, manifest.ItemSpec);
            }

            foreach (var assetpack in specs.OfType<PDictionary> ()) {
                var path = Path.Combine (assetpack.GetString ("bundle-path").Value, "Info.plist");
                var bundlePath = PathUtils.AbsoluteToRelative (intermediate, path);
                var outputPath = Path.Combine (OutputPath, bundlePath);
                var rpath = Path.Combine (intermediate, bundlePath);
                var dict = new PDictionary ();

                dict.SetCFBundleIdentifier (assetpack.GetString ("bundle-id").Value);
                dict.Add ("Tags", assetpack.GetArray ("tags").Clone ());

                dict.Save (path, true, true);

                var item = new TaskItem (rpath);
                item.SetMetadata ("LogicalName", bundlePath);
                item.SetMetadata ("OutputPath", outputPath);
                item.SetMetadata ("Optimize", "false");

                bundleResources.Add (item);
            }

            BundleResources = bundleResources.ToArray ();
            OutputManifests = outputManifests.ToArray ();

            return !Log.HasLoggedErrors;
        }
        protected override bool Compile(PDictionary plist)
        {
            var currentSDK = Sdks.GetAppleSdk(Platform);

            sdkVersion = AppleSdkVersion.Parse(DefaultSdkVersion);
            if (!currentSDK.SdkIsInstalled(sdkVersion, SdkIsSimulator))
            {
                Log.LogError(null, null, null, null, 0, 0, 0, 0, MSBStrings.E0013, Platform, sdkVersion);
                return(false);
            }

            supportedDevices = plist.GetUIDeviceFamily();

            if (!IsWatchApp)
            {
                var version = Sdks.XamIOS.ExtendedVersion;
                // This key is our supported way of determining if an app
                // was built with Xamarin, so it needs to be present in all apps.

                var dict = new PDictionary();
                dict.Add("Version", new PString(string.Format("{0} ({1}: {2})", version.Version, version.Branch, version.Hash)));
                plist.Add("com.xamarin.ios", dict);
            }

            var sdkSettings = currentSDK.GetSdkSettings(sdkVersion, SdkIsSimulator);
            var dtSettings  = currentSDK.GetAppleDTSettings();

            SetValue(plist, ManifestKeys.BuildMachineOSBuild, dtSettings.BuildMachineOSBuild);
            // We have an issue here, this is for consideration by the platform:
            // CFLocaleCopyCurrent(), used in the mono code to get the current locale (locale.c line 421), will return the value of the application's CFBundleDevelopmentRegion Info.plist key if all of the following conditions are true:
            //
            // * CFBundleDevelopmentRegion is present in the Info.plist
            // * The CFBundleDevelopmentRegion language is in the list of preferred languages on the iOS device, but isn't the first one
            // * There are no localized resources (i.e. no .lproj directory) in the app for the first preferred locale
            //
            // This differs from iOS 10 where the presence of the CFBundleDevelopmentRegion key had no effect. Commenting this line out, ensures that CurrentCulture is correct and behaves like the iOS 10 version.
            // plist.SetIfNotPresent (ManifestKeys.CFBundleDevelopmentRegion, "en");

            if (IsIOS)
            {
                var executable = plist.GetCFBundleExecutable();
                if (executable.EndsWith(".app", StringComparison.Ordinal))
                {
                    LogAppManifestError(MSBStrings.E0014, executable);
                }
            }

            if (!string.IsNullOrEmpty(ResourceRules))
            {
                plist.SetIfNotPresent(ManifestKeys.CFBundleResourceSpecification, Path.GetFileName(ResourceRules));
            }
            if (!plist.ContainsKey(ManifestKeys.CFBundleSupportedPlatforms))
            {
                plist[ManifestKeys.CFBundleSupportedPlatforms] = new PArray {
                    SdkPlatform
                }
            }
            ;

            string dtCompiler        = null;
            string dtPlatformBuild   = null;
            string dtSDKBuild        = null;
            string dtPlatformName    = null;
            string dtPlatformVersion = null;
            string dtXcode           = null;
            string dtXcodeBuild      = null;

            if (!SdkIsSimulator)
            {
                dtCompiler      = sdkSettings.DTCompiler;
                dtPlatformBuild = dtSettings.DTPlatformBuild;
                dtSDKBuild      = sdkSettings.DTSDKBuild;
            }

            dtPlatformName = SdkPlatform.ToLowerInvariant();
            if (!SdkIsSimulator)
            {
                dtPlatformVersion = dtSettings.DTPlatformVersion;
            }

            var dtSDKName = sdkSettings.CanonicalName;

            // older sdksettings didn't have a canonicalname for sim
            if (SdkIsSimulator && string.IsNullOrEmpty(dtSDKName))
            {
                var deviceSdkSettings = currentSDK.GetSdkSettings(sdkVersion, false);
                dtSDKName = deviceSdkSettings.AlternateSDK;
            }

            if (!SdkIsSimulator)
            {
                dtXcode      = AppleSdkSettings.DTXcode;
                dtXcodeBuild = dtSettings.DTXcodeBuild;
            }

            SetValueIfNotNull(plist, "DTCompiler", dtCompiler);
            SetValueIfNotNull(plist, "DTPlatformBuild", dtPlatformBuild);
            SetValueIfNotNull(plist, "DTSDKBuild", dtSDKBuild);
            plist.SetIfNotPresent("DTPlatformName", dtPlatformName);
            SetValueIfNotNull(plist, "DTPlatformVersion", dtPlatformVersion);
            SetValue(plist, "DTSDKName", dtSDKName);
            SetValueIfNotNull(plist, "DTXcode", dtXcode);
            SetValueIfNotNull(plist, "DTXcodeBuild", dtXcodeBuild);

            SetDeviceFamily(plist);

            if (IsWatchExtension)
            {
                // Note: Only watchOS1 Extensions target Xamarin.iOS
                if (Platform == ApplePlatform.iOS)
                {
                    PObject value;

                    if (!plist.TryGetValue(ManifestKeys.UIRequiredDeviceCapabilities, out value))
                    {
                        var capabilities = new PArray();
                        capabilities.Add(new PString("watch-companion"));

                        plist.Add(ManifestKeys.UIRequiredDeviceCapabilities, capabilities);
                    }
                    else if (value is PDictionary)
                    {
                        var capabilities = (PDictionary)value;

                        if (!capabilities.ContainsKey("watch-companion"))
                        {
                            capabilities.Add("watch-companion", new PBoolean(true));
                        }
                    }
                    else
                    {
                        var  capabilities = (PArray)value;
                        bool exists       = false;

                        foreach (var capability in capabilities.OfType <PString> ())
                        {
                            if (capability.Value != "watch-companion")
                            {
                                continue;
                            }

                            exists = true;
                            break;
                        }

                        if (!exists)
                        {
                            capabilities.Add(new PString("watch-companion"));
                        }
                    }
                }

                if (Debug)
                {
                    SetAppTransportSecurity(plist);
                }
            }

            SetRequiredArchitectures(plist);

            if (IsIOS)
            {
                Validation(plist);
            }

            return(!Log.HasLoggedErrors);
        }

        void SetValueIfNotNull(PDictionary dict, string key, string value)
        {
            if (value == null)
            {
                return;
            }
            SetValue(dict, key, value);
        }

        void SetRequiredArchitectures(PDictionary plist)
        {
            PObject capabilities;

            if (plist.TryGetValue(ManifestKeys.UIRequiredDeviceCapabilities, out capabilities))
            {
                if (capabilities is PArray)
                {
                    var architectureValues = new HashSet <string> (new[] { "armv6", "armv7", "arm64" });
                    var array = (PArray)capabilities;

                    // Remove any architecture values
                    for (int i = 0; i < array.Count; i++)
                    {
                        var value = array[i] as PString;

                        if (value == null || !architectureValues.Contains(value.Value))
                        {
                            continue;
                        }

                        array.RemoveAt(i);
                    }

                    // If-and-only-if the TargetArchitecture is a single architecture, set it as a required device capability
                    switch (architectures)
                    {
                    case TargetArchitecture.ARM64:
                        array.Add(new PString("arm64"));
                        break;

                    case TargetArchitecture.ARMv7:
                        array.Add(new PString("armv7"));
                        break;
                    }
                }
                else if (capabilities is PDictionary)
                {
                    var dict = (PDictionary)capabilities;

                    switch (architectures)
                    {
                    case TargetArchitecture.ARM64:
                        dict["arm64"] = new PBoolean(true);
                        dict.Remove("armv6");
                        dict.Remove("armv7");
                        break;

                    case TargetArchitecture.ARMv7:
                        dict["armv7"] = new PBoolean(true);
                        dict.Remove("armv6");
                        dict.Remove("arm64");
                        break;

                    default:
                        dict.Remove("armv6");
                        dict.Remove("armv7");
                        dict.Remove("arm64");
                        break;
                    }
                }
            }
            else
            {
                var array = new PArray();

                // If-and-only-if the TargetArchitecture is a single architecture, set it as a required device capability
                switch (architectures)
                {
                case TargetArchitecture.ARM64:
                    array.Add(new PString("arm64"));
                    break;

                case TargetArchitecture.ARMv7:
                    array.Add(new PString("armv7"));
                    break;
                }

                if (array.Count > 0)
                {
                    plist.Add(ManifestKeys.UIRequiredDeviceCapabilities, array);
                }
            }
        }

        void SetDeviceFamily(PDictionary plist)
        {
            switch (Platform)
            {
            case ApplePlatform.iOS:
                SetIOSDeviceFamily(plist);
                break;

            case ApplePlatform.WatchOS:
                plist.SetUIDeviceFamily(IPhoneDeviceType.Watch);
                break;

            case ApplePlatform.TVOS:
                plist.SetUIDeviceFamily(IPhoneDeviceType.TV);
                break;
            }
        }

        void SetIOSDeviceFamily(PDictionary plist)
        {
            if (IsWatchApp)
            {
                if (SdkIsSimulator)
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.IPhone | IPhoneDeviceType.Watch);
                }
                else
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.Watch);
                }
            }
            else
            {
                if (!IsAppExtension)
                {
                    plist.SetIfNotPresent(ManifestKeys.LSRequiresIPhoneOS, true);
                }

                if (supportedDevices == IPhoneDeviceType.NotSet)
                {
                    plist.SetUIDeviceFamily(IPhoneDeviceType.IPhone);
                }
            }
        }

        void SetAppTransportSecurity(PDictionary plist)
        {
            // Debugging over http has a couple of gotchas:
            // * We can't use https, because that requires a valid server certificate,
            //   which we can't ensure.
            //   It would also require a hostname for the mac, which it might not have either.
            // * NSAppTransportSecurity/NSExceptionDomains does not allow exceptions based
            //   on IP address (only hostname).
            // * Which means the only way to make sure watchOS allows connections from
            //   the app on device to the mac is to disable App Transport Security altogether.
            // Good news: watchOS 3 will apparently not apply ATS when connecting
            // directly to IP addresses, which means we won't have to do this at all
            // (sometime in the future).

            PDictionary ats;

            if (!plist.TryGetValue(ManifestKeys.NSAppTransportSecurity, out ats))
            {
                plist.Add(ManifestKeys.NSAppTransportSecurity, ats = new PDictionary());
            }

            if (ats.GetBoolean(ManifestKeys.NSAllowsArbitraryLoads))
            {
                Log.LogMessage(MessageImportance.Low, MSBStrings.M0017);
            }
            else
            {
                Log.LogMessage(MessageImportance.Low, MSBStrings.M0018);
                ats.SetBooleanOrRemove(ManifestKeys.NSAllowsArbitraryLoads, true);
            }
        }

        void Validation(PDictionary plist)
        {
            if (!Validate)
            {
                return;
            }

            var supportsIPhone = (supportedDevices & IPhoneDeviceType.IPhone) != 0 ||
                                 supportedDevices == IPhoneDeviceType.NotSet;
            var supportsIPad = (supportedDevices & IPhoneDeviceType.IPad) != 0;

            // Validation...
            if (!IsAppExtension && sdkVersion >= AppleSdkVersion.V3_2)
            {
                IPhoneOrientation orientation;

                if (supportsIPhone)
                {
                    orientation = plist.GetUISupportedInterfaceOrientations(false);
                    if (orientation == IPhoneOrientation.None)
                    {
                        LogAppManifestWarning(MSBStrings.W0019);
                    }
                    else if (!orientation.IsValidPair())
                    {
                        LogAppManifestWarning(MSBStrings.W0020);
                    }
                }

                if (supportsIPad)
                {
                    orientation = plist.GetUISupportedInterfaceOrientations(true);
                    if (orientation == IPhoneOrientation.None)
                    {
                        LogAppManifestWarning(MSBStrings.W0021);
                    }
                    else if (!orientation.IsValidPair())
                    {
                        LogAppManifestWarning(MSBStrings.W0022);
                    }
                }
            }
        }
    }
        public override bool Execute()
        {
            PDictionary plist;

            if (File.Exists(ProductDefinition))
            {
                try {
                    plist = PDictionary.FromFile(ProductDefinition);
                } catch (Exception ex) {
                    LogProductDefinitionError(MSBStrings.E0010, ProductDefinition, ex.Message);
                    return(false);
                }
            }
            else
            {
                plist = new PDictionary();
            }

            if (!string.IsNullOrEmpty(TargetArchitectures) && !Enum.TryParse(TargetArchitectures, out architectures))
            {
                LogProductDefinitionError(MSBStrings.E0012, TargetArchitectures);
                return(false);
            }

            // productbuild can do a guess of the targeted architectures if not provided, but the guess
            // is very simple : on Catalina and lower, it will suppose it's x86_64 (even with an arm64 slice).
            HashSet <string> archStrings = new HashSet <string> (architectures.ToArray().Select(a => a.ToNativeArchitecture()));

            if (plist.TryGetValue(ProductDefinitionKeys.Architectures, out PArray archArray))
            {
                var existingArchs = archArray.ToStringArray();
                if (!archStrings.SetEquals(existingArchs))
                {
                    LogProductDefinitionWarning(MSBStrings.E7072, string.Join(", ", existingArchs), string.Join(", ", archStrings));
                }
            }

            if (archArray == null)
            {
                archArray = new PArray();
                foreach (var arch in archStrings)
                {
                    archArray.Add(new PString(arch));
                }
            }
            plist [ProductDefinitionKeys.Architectures] = archArray;

            if (!plist.TryGetValue(ProductDefinitionKeys.MinimumSystemVersion, out PArray osVersionArray))
            {
                var minOSVersion = GetMinimumOSVersion();
                if (minOSVersion != null)
                {
                    osVersionArray = new PArray();
                    osVersionArray.Add(new PString(minOSVersion));
                }
            }
            if (osVersionArray != null)
            {
                plist [ProductDefinitionKeys.MinimumSystemVersion] = osVersionArray;
            }

            CompiledProductDefinition = new TaskItem(Path.Combine(OutputDirectory, "Product.plist"));
            plist.Save(CompiledProductDefinition.ItemSpec, true, false);

            return(!Log.HasLoggedErrors);
        }
        public override bool Execute()
        {
            var  manifestPath            = Path.Combine(AppBundleDir.ItemSpec, "AssetPackManifestTemplate.plist");
            var  onDemandResourcesPath   = Path.Combine(AppBundleDir.ItemSpec, "OnDemandResources.plist");
            var  onDemandResourcesDir    = Path.Combine(OutputPath, "OnDemandResources");
            var  onDemandResourcesStamp  = File.GetLastWriteTime(onDemandResourcesPath);
            var  initialInstallTags      = new HashSet <string> (AssetPackUtils.ParseTags(InitialInstallTags));
            var  prefetchOrder           = AssetPackUtils.ParseTags(PrefetchOrder);
            var  manifestStamp           = File.GetLastWriteTime(manifestPath);
            var  onDemandResources       = new PDictionary();
            var  requestTags             = new PDictionary();
            bool updateOnDemandResources = false;
            var  assetPacks     = new PDictionary();
            var  manifest       = new PDictionary();
            var  resources      = new PArray();
            bool updateManifest = false;

            Log.LogTaskName("CreateAssetPackManifest");
            Log.LogTaskProperty("AppBundleDir", AppBundleDir);
            Log.LogTaskProperty("InitialInstallTags", InitialInstallTags);
            Log.LogTaskProperty("OutputPath", OutputPath);
            Log.LogTaskProperty("PrefetchOrder", PrefetchOrder);

            if (!Directory.Exists(onDemandResourcesDir))
            {
                return(!Log.HasLoggedErrors);
            }

            onDemandResources.Add("NSBundleResourceRequestAssetPacks", assetPacks);
            onDemandResources.Add("NSBundleResourceRequestTags", requestTags);

            manifest.Add("resources", resources);

            foreach (var dir in Directory.EnumerateDirectories(onDemandResourcesDir))
            {
                var         path = Path.Combine(dir, "Info.plist");
                PDictionary info;

                if (!File.Exists(path))
                {
                    continue;
                }

                var mtime = File.GetLastWriteTime(path);

                updateOnDemandResources = updateOnDemandResources || mtime > onDemandResourcesStamp;
                updateManifest          = updateManifest || mtime > manifestStamp;

                try {
                    info = PDictionary.FromFile(path);
                } catch {
                    continue;
                }

                var  bundleIdentifier   = info.GetCFBundleIdentifier();
                var  primaryContentHash = new PDictionary();
                var  resource           = new PDictionary();
                var  items = new PArray();
                long size  = 0;

                // update OnDemandResources.plist:NSBundleResourceRequestAssetPacks
                foreach (var file in Directory.EnumerateFiles(dir))
                {
                    var name = Path.GetFileName(file);

                    if (name != "Info.plist")
                    {
                        items.Add(new PString(name));
                    }

                    size += new FileInfo(file).Length;
                }

                assetPacks.Add(bundleIdentifier, items);

                // update OnDemandResources.plist:NSBundleResourceRequestTags
                var tags     = info.GetArray("Tags").OfType <PString> ().Select(x => x.Value);
                var priority = double.NaN;

                foreach (var tag in tags)
                {
                    PDictionary dict;
                    PArray      packs;

                    if (initialInstallTags.Contains(tag))
                    {
                        priority = 1.0f;
                    }
                    else
                    {
                        for (int i = 0; i < prefetchOrder.Length; i++)
                        {
                            if (tag == prefetchOrder[i])
                            {
                                var value = GetDownloadPriority(i, prefetchOrder.Length);

                                priority = double.IsNaN(priority) ? value : Math.Max(priority, value);
                                break;
                            }
                        }
                    }

                    if (!requestTags.TryGetValue(tag, out dict))
                    {
                        dict = new PDictionary();
                        dict.Add("NSAssetPacks", new PArray());

                        requestTags.Add(tag, dict);
                    }

                    packs = dict.GetArray("NSAssetPacks");

                    packs.Add(new PString(bundleIdentifier));
                }

                // update AssetPackManifestTemplate.plist
                resource.Add("URL", new PString("http://127.0.0.1" + Path.GetFullPath(dir)));
                resource.Add("bundleKey", new PString(bundleIdentifier));

                if (!double.IsNaN(priority))
                {
                    resource.Add("downloadPriority", new PReal(priority));
                }

                resource.Add("isStreamable", new PBoolean(true));
                primaryContentHash.Add("hash", mtime.ToString("yyyy-MM-dd HH:mm:ss.000"));
                primaryContentHash.Add("strategy", "modtime");
                resource.Add("primaryContentHash", primaryContentHash);
                resource.Add("uncompressedSize", new PNumber((int)((size + 8191) & ~8191)));

                resources.Add(resource);
            }

            if (updateOnDemandResources)
            {
                try {
                    onDemandResources.Save(onDemandResourcesPath, true, true);
                } catch (Exception ex) {
                    Log.LogError("Error saving `{0}': {1}", onDemandResourcesPath, ex.Message);
                }
            }

            if (updateManifest)
            {
                try {
                    manifest.Save(manifestPath, true, true);
                } catch (Exception ex) {
                    Log.LogError("Error saving `{0}': {1}", manifestPath, ex.Message);
                }
            }

            return(!Log.HasLoggedErrors);
        }
예제 #34
0
        PArray MergeEntitlementArray(PArray array, MobileProvision profile)
        {
            var result = new PArray ();

            foreach (var item in array) {
                PObject value;

                if (item is PDictionary)
                    value = MergeEntitlementDictionary ((PDictionary) item, profile);
                else if (item is PString)
                    value = MergeEntitlementString ((PString) item, profile);
                else if (item is PArray)
                    value = MergeEntitlementArray ((PArray) item, profile);
                else
                    value = item.Clone ();

                if (value != null)
                    result.Add (value);
            }

            if (result.Count > 0)
                return result;

            return null;
        }
		void AddNewArrayElement (PArray array)
		{
			var values = PListScheme.AvailableKeys (array, CurrentTree);
			if (values == null) {
				array.Add (PObject.Create (DefaultNewObjectType));
			} else if (values.Any ()) {
				array.Add (values.First ().Create ());
			}
		}