/// <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)); }
/// <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); } } } }