Ejemplo n.º 1
0
        /// <summary>
        /// This is used to return a composition container filled with the available build components (SHFB
        /// plug-ins, presentation styles, BuildAssembler components, and syntax generators).
        /// </summary>
        /// <param name="folders">An enumerable list of additional folders to search recursively for components.</param>
        /// <param name="cancellationToken">An optional cancellation token or null if not supported by the caller.</param>
        /// <returns>The a composition container that contains all of the available components</returns>
        /// <remarks>The following folders are searched in the following order.  If the given folder has not been
        /// specified or does not exist, it is ignored.
        ///
        /// <list type="number">
        ///     <item>The enumerable list of additional folders - This is typically the current project's
        /// NuGet packages (package tool paths from the <c>SHFBComponentPath</c> item in their properties file),
        /// the project's <c>ComponentPath</c> folder, and the current project's folder.  This allows for
        /// project-specific build components.  Paths are searched in the order given above if specified.</item>
        ///     <item>Common application data folder - The help file builder's common application data folder
        /// where third-party build components are typically installed.</item>
        ///     <item><c>SHFBROOT</c> core components folder - The core Sandcastle Help File Builder components
        /// folder and its subfolders.  This allows for XCOPY deployments that keep everything together.</item>
        /// </list>
        ///
        /// All folders and their subfolders are search recursively for assemblies (*.dll).  There may be
        /// duplicate component IDs across the assemblies found.  Only the first component for a unique
        /// ID will be used.  As such, assemblies in a folder with a higher search precedence can override
        /// copies in folders lower in the search order.</remarks>
        public static CompositionContainer CreateComponentContainer(IEnumerable <string> folders,
                                                                    CancellationToken cancellationToken)
        {
            if (folders == null)
            {
                throw new ArgumentNullException(nameof(folders));
            }

            var catalog = new AggregateCatalog();
            HashSet <string> searchedFolders = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            using (var resolver = new ComponentAssemblyResolver())
            {
                foreach (string folder in folders)
                {
                    AddAssemblyCatalogs(catalog, folder, searchedFolders, true, resolver, cancellationToken);
                }

                AddAssemblyCatalogs(catalog, ThirdPartyComponentsFolder, searchedFolders, true, resolver, cancellationToken);
                AddAssemblyCatalogs(catalog, CoreComponentsFolder, searchedFolders, true, resolver, cancellationToken);
            }

            return(new CompositionContainer(catalog));
        }
Ejemplo n.º 2
0
        /// <summary>
        /// This adds assembly catalogs to the given aggregate catalog for the given folder and all of its
        /// subfolders recursively.
        /// </summary>
        /// <param name="catalog">The aggregate catalog to which the assembly catalogs are added.</param>
        /// <param name="folder">The root folder to search.  It and all subfolders recursively will be searched
        /// for assemblies to add to the aggregate catalog.</param>
        /// <param name="searchedFolders">A hash set of folders that have already been searched and added.</param>
        /// <param name="includeSubfolders">True to search subfolders recursively, false to only search the given
        /// folder.</param>
        /// <param name="resolver">A component assembly resolver for finding dependency assemblies</param>
        /// <param name="cancellationToken">An optional cancellation token or null if not supported by the caller.</param>
        /// <remarks>It is done this way to prevent a single assembly that would normally be discovered via a
        /// directory catalog from preventing all assemblies from loading if it cannot be examined when the parts
        /// are composed (i.e. trying to load a Windows Store assembly on Windows 7).</remarks>
        private static void AddAssemblyCatalogs(AggregateCatalog catalog, string folder,
                                                HashSet <string> searchedFolders, bool includeSubfolders, ComponentAssemblyResolver resolver,
                                                CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (!String.IsNullOrWhiteSpace(folder) && Directory.Exists(folder) && !searchedFolders.Contains(folder))
            {
                searchedFolders.Add(folder);

                // When debugging components, there may be a copy in the .\obj folder which tends to get found
                // later so it is used rather than the one in the .\bin folder which is actually being debugged.
                // If this is an .\obj folder and a .\bin folder has already been seen in the same location,
                // ignore it.
                if (folder.EndsWith("\\obj", StringComparison.OrdinalIgnoreCase) && searchedFolders.Contains(
                        Path.Combine(Path.GetDirectoryName(folder), "bin")))
                {
                    return;
                }

                bool hadComponents = false;

                foreach (var file in Directory.EnumerateFiles(folder, "*.dll"))
                {
                    if (cancellationToken != CancellationToken.None)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    try
                    {
                        var asmCat = new AssemblyCatalog(file);

                        // Force MEF to load the assembly and figure out if there are any exports.  Valid
                        // assemblies won't throw any exceptions and will contain parts and will be added to
                        // the catalog.  Use Count() rather than Any() to ensure it touches all parts in case
                        // that makes a difference.
                        if (asmCat.Parts.Count() > 0)
                        {
                            catalog.Catalogs.Add(asmCat);
                            hadComponents = true;
                        }
                        else
                        {
                            asmCat.Dispose();
                        }
                    }   // Ignore the errors we may expect to see but log them for debugging purposes
                    catch (ArgumentException ex)
                    {
                        // These can occur if it tries to load a foreign framework assembly (i.e. .NETStandard)
                        // In this case, the inner exception will be the bad image format exception.  If not,
                        // report the issue.
                        if (!(ex.InnerException is BadImageFormatException))
                        {
                            throw;
                        }

                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (FileNotFoundException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (FileLoadException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (BadImageFormatException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (IOException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (System.Security.SecurityException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (TypeLoadException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);

                        foreach (var lex in ex.LoaderExceptions)
                        {
                            System.Diagnostics.Debug.WriteLine(lex);
                        }
                    }
                }

                // Track folders with components so that we can search them for dependencies later if needed
                if (hadComponents)
                {
                    resolver.AddFolder(folder);
                }

                // Enumerate subfolders separately so that we can skip future requests for the same folder
                if (includeSubfolders)
                {
                    try
                    {
                        foreach (string subfolder in Directory.EnumerateDirectories(folder, "*", SearchOption.AllDirectories))
                        {
                            AddAssemblyCatalogs(catalog, subfolder, searchedFolders, false, resolver, cancellationToken);
                        }
                    }
                    catch (IOException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (System.Security.SecurityException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                }
            }
        }