Example #1
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 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);
            }
        }
Example #3
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);
        }
Example #4
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);
        }
        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);
        }
Example #6
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;
        }