public static string GetLoaderLibraryPath()
        {
            var extensions = FeatureHelpersInternal.GetAllFeatureInfo(BuildTargetGroup.Standalone);

            // Loop over all the native plugin importers and find the custom loader
            var importers = PluginImporter.GetAllImporters();

            foreach (var importer in importers)
            {
                if (!importer.GetCompatibleWithEditor() || !importer.assetPath.Contains("openxr_loader"))
                {
                    continue;
                }

#if UNITY_EDITOR_WIN
                if (!importer.GetCompatibleWithPlatform(BuildTarget.StandaloneWindows64) || !importer.assetPath.EndsWith(".dll"))
                {
                    continue;
                }
#elif UNITY_EDITOR_OSX
                if (!importer.GetCompatibleWithPlatform(BuildTarget.StandaloneOSX) || !importer.assetPath.EndsWith(".dylib"))
                {
                    continue;
                }
#endif

                bool importerPartOfExtension = false;
                var  root = Path.GetDirectoryName(importer.assetPath);
                foreach (var extInfo in extensions.Features)
                {
                    bool extensionContainsLoader = (root != null && root.Contains(extInfo.PluginPath));
                    importerPartOfExtension |= extensionContainsLoader;

                    bool customRuntimeLoaderOnEditorTarget = extInfo.Attribute.CustomRuntimeLoaderBuildTargets?.Intersect(
                        new[] { BuildTarget.StandaloneWindows64, BuildTarget.StandaloneOSX, BuildTarget.StandaloneLinux64 }).Any() ?? false;

                    if (extensionContainsLoader &&
                        customRuntimeLoaderOnEditorTarget &&
                        extInfo.Feature.enabled)
                    {
                        return(AssetPathToAbsolutePath(importer.assetPath));
                    }
                }

                // return default loader
                bool hasCustomLoader = extensions.CustomLoaderBuildTargets?.Length > 0;
                if (!importerPartOfExtension && !hasCustomLoader)
                {
                    return(AssetPathToAbsolutePath(importer.assetPath));
                }
            }

            return("");
        }
        public void OnPreprocessBuild(BuildReport report)
        {
            var extensions = FeatureHelpersInternal.GetAllFeatureInfo(report.summary.platformGroup);

            // Keep set of seen plugins, only disable plugins that haven't been seen.
            HashSet <string> seenPlugins = new HashSet <string>();

            // Loop over all the native plugin importers and only include the enabled ones in the build
            var importers = PluginImporter.GetAllImporters();

            foreach (var importer in importers)
            {
                if (!importer.GetCompatibleWithPlatform(report.summary.platform))
                {
                    continue;
                }
                if (importer.assetPath.Contains("openxr_loader"))
                {
                    if (extensions.CustomLoaderBuildTargets?.Contains(report.summary.platform) ?? false)
                    {
                        importer.SetIncludeInBuildDelegate(path => false);
                    }
                    else
                    {
                        importer.SetIncludeInBuildDelegate(path => true);
                    }
                }

                var root = Path.GetDirectoryName(importer.assetPath);
                foreach (var extInfo in extensions.Features)
                {
                    if (root != null && root.Contains(extInfo.PluginPath))
                    {
                        if (extInfo.Feature.enabled)
                        {
                            importer.SetIncludeInBuildDelegate(path => true);
                        }
                        else if (!seenPlugins.Contains(importer.assetPath))
                        {
                            importer.SetIncludeInBuildDelegate(path => false);
                        }
                        seenPlugins.Add(importer.assetPath);
                    }
                }
            }
        }
        /// <summary>
        /// Given the current enabled state of the feature sets that match for a build target group, enable and disable the features associated with
        /// each feature set. Features that overlap sets of varying enabled states will maintain their enabled setting.
        /// </summary>
        /// <param name="buildTargetGroup">The build target group to process features sets for.</param>
        public static void SetFeaturesFromEnabledFeatureSets(BuildTargetGroup buildTargetGroup)
        {
            HashSet <string> enabledFeatureIds = new HashSet <string>();
            var extInfo = FeatureHelpersInternal.GetAllFeatureInfo(buildTargetGroup);

            foreach (var ext in extInfo.Features)
            {
                if (ext.Feature.enabled)
                {
                    enabledFeatureIds.Add(ext.Attribute.FeatureId);
                }
            }


            var featureSets = FeatureSetInfosForBuildTarget(buildTargetGroup);

            foreach (var featureSet in featureSets)
            {
                if (featureSet.featureIds == null)
                {
                    continue;
                }

                if (featureSet.isEnabled)
                {
                    enabledFeatureIds.UnionWith(featureSet.featureIds);
                }
                else if (featureSet.wasChanged)
                {
                    enabledFeatureIds.ExceptWith(featureSet.featureIds);
                }

                featureSet.wasChanged = false;
            }

            foreach (var ext in extInfo.Features)
            {
                ext.Feature.enabled = enabledFeatureIds.Contains(ext.Attribute.FeatureId);
            }
        }
 /// <summary>
 /// Discovers all features in project and ensures that OpenXRSettings.Instance.features is up to date
 /// for selected build target group.
 /// </summary>
 /// <param name="group">build target group to refresh</param>
 public static void RefreshFeatures(BuildTargetGroup group)
 {
     FeatureHelpersInternal.GetAllFeatureInfo(group);
 }
        /// <summary>
        /// Given the current enabled state of the feature sets that match for a build target group, enable and disable the features associated with
        /// each feature set. Features that overlap sets of varying enabled states will maintain their enabled setting.
        /// </summary>
        /// <param name="buildTargetGroup">The build target group to process features sets for.</param>
        public static void SetFeaturesFromEnabledFeatureSets(BuildTargetGroup buildTargetGroup)
        {
            var featureSets = FeatureSetInfosForBuildTarget(buildTargetGroup);
            var extInfo     = FeatureHelpersInternal.GetAllFeatureInfo(buildTargetGroup);

            var fsi = s_FeatureSetState[buildTargetGroup];

            fsi.featureSetFeatureIds.Clear();
            foreach (var featureSet in featureSets)
            {
                if (featureSet.featureIds != null)
                {
                    fsi.featureSetFeatureIds.UnionWith(featureSet.featureIds);
                }
            }

            fsi.featureSetFeatureIds.Clear();
            fsi.requiredToEnabledFeatureIds.Clear();
            fsi.requiredToDisabledFeatureIds.Clear();
            fsi.defaultToEnabledFeatureIds.Clear();

            // Update the selected feature set states first
            foreach (var featureSet in featureSets)
            {
                if (featureSet.featureIds == null)
                {
                    continue;
                }

                OpenXREditorSettings.Instance.SetFeatureSetSelected(buildTargetGroup, featureSet.featureSetId, featureSet.isEnabled);
            }

            foreach (var featureSet in featureSets)
            {
                if (featureSet.featureIds == null)
                {
                    continue;
                }

                if (featureSet.isEnabled && featureSet.requiredFeatureIds != null)
                {
                    fsi.requiredToEnabledFeatureIds.UnionWith(featureSet.requiredFeatureIds);
                }

                if (featureSet.isEnabled != featureSet.wasEnabled)
                {
                    if (featureSet.isEnabled && featureSet.defaultFeatureIds != null)
                    {
                        fsi.defaultToEnabledFeatureIds.UnionWith(featureSet.defaultFeatureIds);
                    }
                    else if (!featureSet.isEnabled && featureSet.requiredFeatureIds != null)
                    {
                        fsi.requiredToDisabledFeatureIds.UnionWith(featureSet.requiredFeatureIds);
                    }

                    featureSet.wasEnabled = featureSet.isEnabled;
                }
            }

            foreach (var ext in extInfo.Features)
            {
                if (ext.Feature.enabled && fsi.requiredToDisabledFeatureIds.Contains(ext.Attribute.FeatureId))
                {
                    ext.Feature.enabled = false;
                }

                if (!ext.Feature.enabled && fsi.requiredToEnabledFeatureIds.Contains(ext.Attribute.FeatureId))
                {
                    ext.Feature.enabled = true;
                }

                if (!ext.Feature.enabled && fsi.defaultToEnabledFeatureIds.Contains(ext.Attribute.FeatureId))
                {
                    ext.Feature.enabled = true;
                }
            }

            s_FeatureSetState[buildTargetGroup] = fsi;

            onFeatureSetStateChanged?.Invoke(buildTargetGroup);
        }