/// <summary>
        /// Apply registry changes to the projects manifest.
        /// </summary>
        /// <param name="manifestModifier">Object that modifies the project's manifest.</param>
        /// <param name="availableRegistries">Registries that are available in the
        /// configuration.</param>
        /// <param name="manifestRegistries">Registries that are present in the manifest.</param>
        /// <param name="selectedRegistryUrls">URLs of selected registries, these should be items in
        /// availableRegistries.</param>
        /// <param name="addRegistries">Whether to add selected registries to the manifest.</param>
        /// <param name="removeRegistries">Whether to remove unselected registries from the
        /// manifest.</param>
        /// <param name="invertSelection">If false, adds the selected registries and removes the
        /// unselected registries.  If true, removes the selected registries and adds the unselected
        /// registries.</param>
        /// <param name="addedRegistries">If specified, is extended with the list of registries added
        /// to the manifest.<param>
        /// <returns>true if successful, false otherwise.</returns>
        private static bool SyncRegistriesToManifest(
            PackageManifestModifier manifestModifier,
            Dictionary <string, PackageManagerRegistry> availableRegistries,
            Dictionary <string, List <PackageManagerRegistry> > manifestRegistries,
            HashSet <string> selectedRegistryUrls,
            bool addRegistries    = true,
            bool removeRegistries = true,
            bool invertSelection  = false,
            List <PackageManagerRegistry> addedRegistries = null)
        {
            List <PackageManagerRegistry> registriesToAdd;
            List <PackageManagerRegistry> registriesToRemove;
            bool manifestModified = SyncRegistriesToModifier(
                manifestModifier, out registriesToAdd, out registriesToRemove,
                availableRegistries, manifestRegistries, selectedRegistryUrls,
                addRegistries, removeRegistries, invertSelection);

            bool successful = true;

            if (manifestModified)
            {
                successful = manifestModifier.WriteManifest();
                if (successful)
                {
                    if (registriesToAdd.Count > 0)
                    {
                        logger.Log(String.Format(
                                       "Added registries to {0}:\n{1}",
                                       PackageManifestModifier.MANIFEST_FILE_PATH,
                                       PackageManagerRegistry.ToString(registriesToAdd)));
                        if (addedRegistries != null)
                        {
                            addedRegistries.AddRange(registriesToAdd);
                        }
                    }
                    if (registriesToRemove.Count > 0)
                    {
                        logger.Log(String.Format(
                                       "Removed registries from {0}:\n{1}",
                                       PackageManifestModifier.MANIFEST_FILE_PATH,
                                       PackageManagerRegistry.ToString(registriesToRemove)));
                    }
                    analytics.Report(
                        "registry_manifest/write/success",
                        new KeyValuePair <string, string>[] {
                        new KeyValuePair <string, string>("added", registriesToAdd.Count.ToString()),
                        new KeyValuePair <string, string>("removed",
                                                          registriesToRemove.Count.ToString())
                    },
                        "Project Manifest Modified");
                }
                else
                {
                    analytics.Report("registry_manifest/write/failed", "Project Manifest Write Failed");
                }
            }
            return(successful);
        }
        /// <summary>
        /// Apply registry changes to the modifier.
        /// </summary>
        /// <param name="manifestModifier">Object that modifies the project's manifest.</param>
        /// <param name="registriesToAdd">Registries added to the modifier.</param>
        /// <param name="registriesToRemove">Registries removed from the modifier.</param>
        /// <param name="availableRegistries">Registries that are available in the
        /// configuration.</param>
        /// <param name="manifestRegistries">Registries that are present in the manifest.</param>
        /// <param name="selectedRegistryUrls">URLs of selected registries, these should be items in
        /// availableRegistries.</param>
        /// <param name="addRegistries">Whether to add selected registries to the manifest.</param>
        /// <param name="removeRegistries">Whether to remove unselected registries from the
        /// manifest.</param>
        /// <param name="invertSelection">If false, adds the selected registries and removes the
        /// unselected registries.  If true, removes the selected registries and adds the unselected
        /// registries.</param>
        /// <returns>true if the manifest is modified.</returns>
        private static bool SyncRegistriesToModifier(
            PackageManifestModifier manifestModifier,
            out List <PackageManagerRegistry> registriesToAdd,
            out List <PackageManagerRegistry> registriesToRemove,
            Dictionary <string, PackageManagerRegistry> availableRegistries,
            Dictionary <string, List <PackageManagerRegistry> > manifestRegistries,
            HashSet <string> selectedRegistryUrls,
            bool addRegistries    = true,
            bool removeRegistries = true,
            bool invertSelection  = false)
        {
            // Build a list of registries to add to and remove from the modifier.
            registriesToAdd    = new List <PackageManagerRegistry>();
            registriesToRemove = new List <PackageManagerRegistry>();

            foreach (var availableRegistry in availableRegistries.Values)
            {
                var  url        = availableRegistry.Url;
                bool isSelected = selectedRegistryUrls.Contains(url);
                if (invertSelection)
                {
                    isSelected = !isSelected;
                }

                bool currentlyInManifest = manifestRegistries.ContainsKey(url);

                if (isSelected)
                {
                    if (addRegistries && !currentlyInManifest)
                    {
                        registriesToAdd.Add(availableRegistry);
                    }
                }
                else
                {
                    if (removeRegistries && currentlyInManifest)
                    {
                        registriesToRemove.Add(availableRegistry);
                    }
                }
            }

            bool manifestModified = false;

            if (registriesToAdd.Count > 0)
            {
                manifestModifier.AddRegistries(registriesToAdd);
                manifestModified = true;
            }
            if (registriesToRemove.Count > 0)
            {
                manifestModifier.RemoveRegistries(registriesToRemove);
                manifestModified = true;
            }
            return(manifestModified);
        }
 /// <summary>
 /// Construct an object based on another modifier.
 /// </summary>
 public PackageManifestModifier(PackageManifestModifier other)
 {
     Logger = other.Logger;
     try {
         manifestDict = Json.Deserialize(other.GetManifestJson()) as Dictionary <string, object>;
     } catch (Exception e) {
         Logger.Log(String.Format("Failed to clone PackageManifestModifier. \nException:{1}",
                                  MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error);
     }
 }
        /// <summary>
        /// Update manifest file based on the settings.
        /// </summary>
        /// <param name="mode">Manifest modification mode being applied.</param>
        /// <param name="promptBeforeAction">Whether to display a window that prompts the user for
        /// confirmation before applying changes.</param>
        /// <param name="showDisableButton">Whether to show a button to disable auto-registry
        /// addition.</param>
        /// <param name="scopePrefixFilter">List of scope prefixes used to filter the set of registries
        /// being operated on.</param>
        internal static void UpdateManifest(ManifestModificationMode mode,
                                            bool promptBeforeAction = true,
                                            bool showDisableButton  = false,
                                            IEnumerable <string> scopePrefixFilter = null)
        {
            if (!ScopedRegistriesSupported)
            {
                logger.Log(String.Format("Scoped registries not supported in this version of Unity."),
                           level: LogLevel.Verbose);
                return;
            }

            PackageManifestModifier modifier = new PackageManifestModifier()
            {
                Logger = logger
            };
            Dictionary <string, List <PackageManagerRegistry> > manifestRegistries =
                modifier.ReadManifest() ? modifier.PackageManagerRegistries : null;

            if (manifestRegistries == null)
            {
                PackageManagerResolver.analytics.Report(
                    "registry_manifest/read/failed",
                    "Update Manifest failed: Read/Parse manifest failed");
                return;
            }

            var xmlRegistries = ReadRegistriesFromXml();

            // Filter registries using the scope prefixes.
            if (scopePrefixFilter != null)
            {
                foreach (var registry in new List <PackageManagerRegistry>(xmlRegistries.Values))
                {
                    bool removeRegistry = true;
                    foreach (var scope in registry.Scopes)
                    {
                        foreach (var scopePrefix in scopePrefixFilter)
                        {
                            if (scope.StartsWith(scopePrefix))
                            {
                                removeRegistry = false;
                            }
                        }
                    }
                    if (removeRegistry)
                    {
                        xmlRegistries.Remove(registry.Url);
                    }
                }
            }

            // Filter the set of considered registries based upon the modification mode.
            HashSet <string> selectedRegistryUrls = null;

            switch (mode)
            {
            case ManifestModificationMode.Add:
                // Remove all items from the XML loaded registries that are present in the manifest.
                foreach (var url in manifestRegistries.Keys)
                {
                    xmlRegistries.Remove(url);
                }
                selectedRegistryUrls = new HashSet <string>(xmlRegistries.Keys);
                break;

            case ManifestModificationMode.Remove:
                // Remove all items from the XML loaded registries that are not present in the
                // manifest.
                foreach (var url in new List <string>(xmlRegistries.Keys))
                {
                    if (!manifestRegistries.ContainsKey(url))
                    {
                        xmlRegistries.Remove(url);
                    }
                }
                selectedRegistryUrls = new HashSet <string>(xmlRegistries.Keys);
                break;

            case ManifestModificationMode.Modify:
                selectedRegistryUrls = new HashSet <string>();
                // Keep all XML loaded registries and select the items in the manifest.
                foreach (var url in xmlRegistries.Keys)
                {
                    if (manifestRegistries.ContainsKey(url))
                    {
                        selectedRegistryUrls.Add(url);
                    }
                }
                break;
            }

            // Applies the manifest modification based upon the modification mode.
            Action <HashSet <string> > syncRegistriesToManifest = (urlSelectionToApply) => {
                var addedRegistries = new List <PackageManagerRegistry>();
                SyncRegistriesToManifest(modifier, xmlRegistries, manifestRegistries,
                                         urlSelectionToApply,
                                         addRegistries: (mode == ManifestModificationMode.Add ||
                                                         mode == ManifestModificationMode.Modify),
                                         removeRegistries: (mode == ManifestModificationMode.Remove ||
                                                            mode == ManifestModificationMode.Modify),
                                         invertSelection: mode == ManifestModificationMode.Remove,
                                         addedRegistries: addedRegistries);
                // If any registries were added try migration if enabled.
                if (addedRegistries.Count > 0 && PromptToMigratePackages)
                {
                    PackageMigrator.MigratePackages();
                }
            };

            // Get the manifest json string based on the current selection and mode.
            Func <HashSet <string>, string> getManifestJsonAfterChange = (urlSelectionToApply) => {
                PackageManifestModifier       clonedModifier = new PackageManifestModifier(modifier);
                List <PackageManagerRegistry> toAdd;
                List <PackageManagerRegistry> toRemove;
                SyncRegistriesToModifier(clonedModifier, out toAdd, out toRemove,
                                         xmlRegistries, manifestRegistries,
                                         urlSelectionToApply,
                                         addRegistries: (mode == ManifestModificationMode.Add ||
                                                         mode == ManifestModificationMode.Modify),
                                         removeRegistries: (mode == ManifestModificationMode.Remove ||
                                                            mode == ManifestModificationMode.Modify),
                                         invertSelection: mode == ManifestModificationMode.Remove);
                return(clonedModifier.GetManifestJson());
            };

            if (xmlRegistries.Count > 0)
            {
                if (promptBeforeAction)
                {
                    // Build a list of items to display.
                    var registryItems = new List <KeyValuePair <string, string> >();
                    foreach (var kv in xmlRegistries)
                    {
                        registryItems.Add(new KeyValuePair <string, string>(kv.Key, kv.Value.Name));
                    }

                    // Optional when prompting is enabled or forced.
                    var window =
                        MultiSelectWindow.CreateMultiSelectWindow <PackageManagerResolverWindow>(
                            PLUGIN_NAME);
                    window.minSize        = new Vector2(1024, 500);
                    window.AvailableItems = registryItems;
                    window.Sort(1);
                    window.SelectedItems = selectedRegistryUrls;
                    switch (mode)
                    {
                    case ManifestModificationMode.Add:
                        window.Caption = String.Format("{0}\n\n{1}\n\n{2}",
                                                       ADD_REGISTRIES_QUESTION,
                                                       ADD_REGISTRIES_DESCRIPTION,
                                                       MODIFY_MENU_ITEM_DESCRIPTION);
                        window.ApplyLabel = "Add Selected Registries";
                        break;

                    case ManifestModificationMode.Remove:
                        window.Caption = String.Format("{0}\n\n{1}{2}",
                                                       REMOVE_REGISTRIES_QUESTION,
                                                       REMOVE_REGISTRIES_DESCRIPTION,
                                                       MODIFY_MENU_ITEM_DESCRIPTION);
                        window.ApplyLabel = "Remove Selected Registries";
                        break;

                    case ManifestModificationMode.Modify:
                        window.Caption = String.Format("{0}\n\n{1} {2}",
                                                       ADD_OR_REMOVE_REGISTRIES_QUESTION,
                                                       ADD_REGISTRIES_DESCRIPTION,
                                                       REMOVE_REGISTRIES_DESCRIPTION);
                        window.ApplyLabel = "Modify Registries";
                        break;
                    }
                    window.RenderItem = (item) => {
                        var registry       = xmlRegistries[item.Key];
                        var termsOfService = registry.TermsOfService;
                        if (!String.IsNullOrEmpty(termsOfService))
                        {
                            if (GUILayout.Button("View Terms of Service"))
                            {
                                Application.OpenURL(termsOfService);
                            }
                        }
                        var privacyPolicy = registry.PrivacyPolicy;
                        if (!String.IsNullOrEmpty(privacyPolicy))
                        {
                            if (GUILayout.Button("View Privacy Policy"))
                            {
                                Application.OpenURL(privacyPolicy);
                            }
                        }
                    };
                    // Set the scroll position to the bottom since "scopedRegistry" section is most
                    // likely at the bottom of the file.
                    scrollManifestViewLeft  = new Vector2(0.0f, float.PositiveInfinity);
                    scrollManifestViewRight = new Vector2(0.0f, float.PositiveInfinity);

                    // Render the change in manifest.json dynamically.
                    window.RenderAfterItems = () => {
                        GUILayout.Label("Changes to Packages/manifest.json");
                        EditorGUILayout.Space();

                        EditorGUILayout.BeginHorizontal();

                        EditorGUILayout.BeginVertical();
                        GUILayout.Label("Before", EditorStyles.boldLabel);
                        EditorGUILayout.Space();
                        scrollManifestViewLeft =
                            EditorGUILayout.BeginScrollView(scrollManifestViewLeft,
                                                            GUILayout.MaxWidth(window.position.width / 2.0f));
                        EditorGUILayout.TextArea(modifier.GetManifestJson());
                        EditorGUILayout.EndScrollView();
                        EditorGUILayout.EndVertical();

                        EditorGUILayout.Space();

                        EditorGUILayout.BeginVertical();
                        GUILayout.Label("After", EditorStyles.boldLabel);
                        EditorGUILayout.Space();
                        scrollManifestViewRight =
                            EditorGUILayout.BeginScrollView(scrollManifestViewRight,
                                                            GUILayout.MaxWidth(window.position.width / 2.0f));
                        EditorGUILayout.TextArea(getManifestJsonAfterChange(window.SelectedItems));
                        EditorGUILayout.EndScrollView();
                        EditorGUILayout.EndVertical();

                        EditorGUILayout.EndHorizontal();
                    };
                    if (showDisableButton)
                    {
                        window.RenderBeforeCancelApply = () => {
                            if (GUILayout.Button("Disable Registry Addition"))
                            {
                                Enable = false;
                                window.Close();
                            }
                        };
                    }
                    window.OnApply = () => { syncRegistriesToManifest(window.SelectedItems); };
                    window.Show();
                }
                else
                {
                    syncRegistriesToManifest(selectedRegistryUrls);
                }
            }
        }
示例#5
0
        /// <summary>
        /// Update manifest file based on the settings.
        /// </summary>
        /// <param name="mode">Manifest modification mode being applied.</param>
        /// <param name="promptBeforeAction">Whether to display a window that prompts the user for
        /// confirmation before applying changes.</param>
        /// <param name="showDisableButton">Whether to show a button to disable auto-registry
        /// addition.</param>
        /// <param name="scopePrefixFilter">List of scope prefixes used to filter the set of registries
        /// being operated on.</param>
        private static void UpdateManifest(ManifestModificationMode mode,
                                           bool promptBeforeAction = true,
                                           bool showDisableButton  = false,
                                           IEnumerable <string> scopePrefixFilter = null)
        {
            if (!ScopedRegistriesSupported)
            {
                logger.Log(String.Format("Scoped registries not supported in this version of Unity."),
                           level: LogLevel.Verbose);
                return;
            }

            PackageManifestModifier modifier = new PackageManifestModifier()
            {
                Logger = logger
            };
            Dictionary <string, List <UnityPackageManagerRegistry> > manifestRegistries =
                modifier.ReadManifest() ? modifier.UnityPackageManagerRegistries : null;

            if (manifestRegistries == null)
            {
                UnityPackageManagerResolver.analytics.Report(
                    "registry_manifest/read/failed",
                    "Update Manifest failed: Read/Parse manifest failed");
                return;
            }

            var xmlRegistries = ReadRegistriesFromXml();

            // Filter registries using the scope prefixes.
            if (scopePrefixFilter != null)
            {
                foreach (var registry in new List <UnityPackageManagerRegistry>(xmlRegistries.Values))
                {
                    bool removeRegistry = true;
                    foreach (var scope in registry.Scopes)
                    {
                        foreach (var scopePrefix in scopePrefixFilter)
                        {
                            if (scope.StartsWith(scopePrefix))
                            {
                                removeRegistry = false;
                            }
                        }
                    }
                    if (removeRegistry)
                    {
                        xmlRegistries.Remove(registry.Url);
                    }
                }
            }

            // Filter the set of considered registries based upon the modification mode.
            HashSet <string> selectedRegistryUrls = null;

            switch (mode)
            {
            case ManifestModificationMode.Add:
                // Remove all items from the XML loaded registries that are present in the manifest.
                foreach (var url in manifestRegistries.Keys)
                {
                    xmlRegistries.Remove(url);
                }
                selectedRegistryUrls = new HashSet <string>(xmlRegistries.Keys);
                break;

            case ManifestModificationMode.Remove:
                // Remove all items from the XML loaded registries that are not present in the
                // manifest.
                foreach (var url in new List <string>(xmlRegistries.Keys))
                {
                    if (!manifestRegistries.ContainsKey(url))
                    {
                        xmlRegistries.Remove(url);
                    }
                }
                selectedRegistryUrls = new HashSet <string>(xmlRegistries.Keys);
                break;

            case ManifestModificationMode.Modify:
                selectedRegistryUrls = new HashSet <string>();
                // Keep all XML loaded registries and select the items in the manifest.
                foreach (var url in xmlRegistries.Keys)
                {
                    if (manifestRegistries.ContainsKey(url))
                    {
                        selectedRegistryUrls.Add(url);
                    }
                }
                break;
            }

            // Applies the manifest modification based upon the modification mode.
            Action <HashSet <string> > syncRegistriesToManifest = (urlSelectionToApply) => {
                SyncRegistriesToManifest(modifier, xmlRegistries, manifestRegistries,
                                         urlSelectionToApply,
                                         addRegistries: (mode == ManifestModificationMode.Add ||
                                                         mode == ManifestModificationMode.Modify),
                                         removeRegistries: (mode == ManifestModificationMode.Remove ||
                                                            mode == ManifestModificationMode.Modify),
                                         invertSelection: mode == ManifestModificationMode.Remove);
            };

            if (xmlRegistries.Count > 0)
            {
                if (promptBeforeAction)
                {
                    // Build a list of items to display.
                    var registryItems = new List <KeyValuePair <string, string> >();
                    foreach (var kv in xmlRegistries)
                    {
                        registryItems.Add(new KeyValuePair <string, string>(kv.Key, kv.Value.Name));
                    }

                    // Optional when prompting is enabled or forced.
                    var window = MultiSelectWindow.CreateMultiSelectWindow(PLUGIN_NAME);
                    window.minSize        = new Vector2(1024, 500);
                    window.AvailableItems = registryItems;
                    window.Sort(1);
                    window.SelectedItems = selectedRegistryUrls;
                    switch (mode)
                    {
                    case ManifestModificationMode.Add:
                        window.Caption = String.Format("{0}\n\n{1}\n\n{2}",
                                                       ADD_REGISTRIES_QUESTION,
                                                       ADD_REGISTRIES_DESCRIPTION,
                                                       MODIFY_MENU_ITEM_DESCRIPTION);
                        window.ApplyLabel = "Add Selected Registries";
                        break;

                    case ManifestModificationMode.Remove:
                        window.Caption = String.Format("{0}\n\n{1}{2}",
                                                       REMOVE_REGISTRIES_QUESTION,
                                                       REMOVE_REGISTRIES_DESCRIPTION,
                                                       MODIFY_MENU_ITEM_DESCRIPTION);
                        window.ApplyLabel = "Remove Selected Registries";
                        break;

                    case ManifestModificationMode.Modify:
                        window.Caption = String.Format("{0}\n\n{1} {2}",
                                                       ADD_OR_REMOVE_REGISTRIES_QUESTION,
                                                       ADD_REGISTRIES_DESCRIPTION,
                                                       REMOVE_REGISTRIES_DESCRIPTION);
                        window.ApplyLabel = "Modify Registries";
                        break;
                    }
                    window.RenderItem = (item) => {
                        var registry       = xmlRegistries[item.Key];
                        var termsOfService = registry.TermsOfService;
                        if (!String.IsNullOrEmpty(termsOfService))
                        {
                            if (GUILayout.Button("View Terms of Service"))
                            {
                                Application.OpenURL(termsOfService);
                            }
                        }
                        var privacyPolicy = registry.PrivacyPolicy;
                        if (!String.IsNullOrEmpty(privacyPolicy))
                        {
                            if (GUILayout.Button("View Privacy Policy"))
                            {
                                Application.OpenURL(privacyPolicy);
                            }
                        }
                    };
                    if (showDisableButton)
                    {
                        window.RenderBeforeCancelApply = () => {
                            if (GUILayout.Button("Disable Registry Addition"))
                            {
                                Enable = false;
                                window.Close();
                            }
                        };
                    }
                    window.OnApply = () => { syncRegistriesToManifest(window.SelectedItems); };
                    window.Show();
                }
                else
                {
                    syncRegistriesToManifest(selectedRegistryUrls);
                }
            }
        }
示例#6
0
        /// <summary>
        /// Apply registry changes to the projects manifest.
        /// </summary>
        /// <param name="manifestModifier">Object that modifies the project's manifest.</param>
        /// <param name="availableRegistries">Registries that are available in the
        /// configuration.</param>
        /// <param name="manifestRegistries">Registries that are present in the manifest.</param>
        /// <param name="selectedRegistryUrls">URLs of selected registries, these should be items in
        /// availableRegistries.</param>
        /// <param name="addRegistries">Whether to add selected registries to the manifest.</param>
        /// <param name="removeRegistries">Whether to remove unselected registries from the
        /// manifest.</param>
        /// <param name="invertSelection">If false, adds the selected registries and removes the
        /// unselected registries.  If true, removes the selected registries and adds the unselected
        /// registries.</param>
        /// <returns>true if successful, false otherwise.</returns>
        private static bool SyncRegistriesToManifest(
            PackageManifestModifier manifestModifier,
            Dictionary <string, UnityPackageManagerRegistry> availableRegistries,
            Dictionary <string, List <UnityPackageManagerRegistry> > manifestRegistries,
            HashSet <string> selectedRegistryUrls,
            bool addRegistries    = true,
            bool removeRegistries = true,
            bool invertSelection  = false)
        {
            // Build a list of registries to add to and remove from the manifest.
            var registriesToAdd    = new List <UnityPackageManagerRegistry>();
            var registriesToRemove = new List <UnityPackageManagerRegistry>();

            foreach (var availableRegistry in availableRegistries.Values)
            {
                var  url        = availableRegistry.Url;
                bool isSelected = selectedRegistryUrls.Contains(url);
                if (invertSelection)
                {
                    isSelected = !isSelected;
                }

                bool currentlyInManifest = manifestRegistries.ContainsKey(url);

                if (isSelected)
                {
                    if (addRegistries && !currentlyInManifest)
                    {
                        registriesToAdd.Add(availableRegistry);
                    }
                }
                else
                {
                    if (removeRegistries && currentlyInManifest)
                    {
                        registriesToRemove.Add(availableRegistry);
                    }
                }
            }

            bool manifestModified = false;

            if (registriesToAdd.Count > 0)
            {
                manifestModifier.AddRegistries(registriesToAdd);
                manifestModified = true;
            }
            if (registriesToRemove.Count > 0)
            {
                manifestModifier.RemoveRegistries(registriesToRemove);
                manifestModified = true;
            }

            bool successful = true;

            if (manifestModified)
            {
                successful = manifestModifier.WriteManifest();
                if (successful)
                {
                    if (registriesToAdd.Count > 0)
                    {
                        logger.Log(String.Format(
                                       "Added registries to {0}:\n{1}",
                                       PackageManifestModifier.MANIFEST_FILE_PATH,
                                       UnityPackageManagerRegistry.ToString(registriesToAdd)));
                    }
                    if (registriesToRemove.Count > 0)
                    {
                        logger.Log(String.Format(
                                       "Removed registries from {0}:\n{1}",
                                       PackageManifestModifier.MANIFEST_FILE_PATH,
                                       UnityPackageManagerRegistry.ToString(registriesToRemove)));
                    }
                    analytics.Report(
                        "registry_manifest/write/success",
                        new KeyValuePair <string, string>[] {
                        new KeyValuePair <string, string>("added", registriesToAdd.Count.ToString()),
                        new KeyValuePair <string, string>("removed",
                                                          registriesToRemove.Count.ToString())
                    },
                        "Project Manifest Modified");
                }
                else
                {
                    analytics.Report("registry_manifest/write/failed", "Project Manifest Write Failed");
                }
            }
            return(successful);
        }