/// <summary>
        /// This is the method that indexes the comments files
        /// </summary>
        /// <remarks>Rather than a partial build, we'll just index the comments files.</remarks>
        private IndexedCommentsCache IndexComments()
        {
            HashSet<string> projectDictionary = new HashSet<string>();
            IndexedCommentsCache cache = new IndexedCommentsCache(100);
            MSBuildProject projRef;
            string lastSolution = null;

            // Index the framework comments based on the framework version in the project
            FrameworkSettings frameworkSettings = FrameworkDictionary.AllFrameworks.GetFrameworkWithRedirect(
                currentProject.FrameworkVersion);

            if(frameworkSettings == null)
                throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
                    "Unable to locate information for a framework version or its redirect: {0}",
                    currentProject.FrameworkVersion));

            foreach(var location in frameworkSettings.CommentsFileLocations(currentProject.Language))
            {
                indexTokenSource.Token.ThrowIfCancellationRequested();

                cache.IndexCommentsFiles(Path.GetDirectoryName(location), Path.GetFileName(location),
                    false, null);
            }

            // Index the comments file documentation sources
            foreach(string file in currentProject.DocumentationSources.SelectMany(ds => ds.CommentsFiles))
            {
                indexTokenSource.Token.ThrowIfCancellationRequested();

                cache.IndexCommentsFiles(Path.GetDirectoryName(file), Path.GetFileName(file), false, null);
            }

            // Also, index the comments files in project documentation sources
            foreach(DocumentationSource ds in currentProject.DocumentationSources)
                foreach(var sourceProject in ds.Projects(
                  !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : currentProject.Configuration,
                  !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : currentProject.Platform))
                {
                    indexTokenSource.Token.ThrowIfCancellationRequested();

                    // NOTE: This code should be similar to the code in BuildProcess.ValidateDocumentationSources!

                    // Solutions are followed by the projects that they contain
                    if(sourceProject.ProjectFileName.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
                    {
                        lastSolution = sourceProject.ProjectFileName;
                        continue;
                    }

                    // Ignore projects that we've already seen
                    if(projectDictionary.Add(sourceProject.ProjectFileName))
                    {
                        using(projRef = new MSBuildProject(sourceProject.ProjectFileName))
                        {
                            // Use the project file configuration and platform properties if they are set.  If
                            // not, use the documentation source values.  If they are not set, use the SHFB
                            // project settings.
                            projRef.SetConfiguration(
                                !String.IsNullOrEmpty(sourceProject.Configuration) ? sourceProject.Configuration :
                                    !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : currentProject.Configuration,
                                !String.IsNullOrEmpty(sourceProject.Platform) ? sourceProject.Platform :
                                    !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : currentProject.Platform,
                                currentProject.MSBuildOutDir, false);

                            // Add Visual Studio solution macros if necessary
                            if(lastSolution != null)
                                projRef.SetSolutionMacros(lastSolution);

                            if(!String.IsNullOrEmpty(projRef.XmlCommentsFile))
                                cache.IndexCommentsFiles(Path.GetDirectoryName(projRef.XmlCommentsFile),
                                    Path.GetFileName(projRef.XmlCommentsFile), false, null);
                        }
                    }
                }

            return cache;
        }
        /// <summary>
        /// This is used to get the build item meta data from the referenced
        /// project.
        /// </summary>
        /// <param name="refresh">True to force the metadata to be refreshed,
        /// false to only retrieve it if it doesn't exist.</param>
        private void GetProjectMetadata(bool refresh)
        {
            string name;

            if(!refresh && base.ProjectElement.HasMetadata(ProjectElement.Name) &&
              base.ProjectElement.HasMetadata(ProjectElement.ProjectGuid))
                return;

            using(MSBuildProject project = new MSBuildProject(projectPath))
            {
                project.SetConfiguration(SandcastleProject.DefaultConfiguration,
                    SandcastleProject.DefaultPlatform, null);

                name = Path.GetFileNameWithoutExtension(project.AssemblyName);

                if(!String.IsNullOrEmpty(name))
                    base.ProjectElement.SetMetadata(ProjectElement.Name, name);
                else
                    base.ProjectElement.SetMetadata(ProjectElement.Name, "(Invalid project type)");

                base.ProjectElement.SetMetadata(ProjectElement.ProjectGuid, project.ProjectGuid);
            }
        }
        //=====================================================================
        /// <summary>
        /// Validate the documentation source information and copy the files to
        /// the working folder.
        /// </summary>
        /// <exception cref="BuilderException">This is thrown if any of
        /// the information is invalid.</exception>
        protected void ValidateDocumentationSources()
        {
            List<string> commentsList = new List<string>();
            Dictionary<string, MSBuildProject> projectDictionary = new Dictionary<string, MSBuildProject>();

            MSBuildProject projRef;
            BuildItem buildItem;
            XPathDocument testComments;
            XPathNavigator navComments;
            XmlCommentsFile comments;
            int fileCount;
            string workingPath, projFramework, hintPath, lastSolution = null,
                targetFramework = project.FrameworkVersion;

            this.ReportProgress(BuildStep.ValidatingDocumentationSources,
                "Validating and copying documentation source information");

            assembliesList = new Collection<string>();
            referenceDictionary = new Dictionary<string, BuildItem>();
            commentsFiles = new XmlCommentsFileCollection();

            if(this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                return;

            // It's possible a plug-in might want to add or remove assemblies
            // so we'll run them before checking to see if the project has any.
            this.ExecutePlugIns(ExecutionBehaviors.Before);

            if(project.DocumentationSources.Count == 0)
                throw new BuilderException("BE0039", "The project does " +
                    "not have any documentation sources defined");

            // Clone the project's references
            foreach(string refType in (new string[] { "Reference", "COMReference" }))
                foreach(BuildItem reference in project.MSBuildProject.GetEvaluatedItemsByName(refType))
                {
                    buildItem = reference.Clone();

                    // Make sure hint paths are correct by adding the project folder to any relative
                    // paths.  Skip any containing MSBuild variable references.
                    if(buildItem.HasMetadata(ProjectElement.HintPath))
                    {
                        hintPath = buildItem.GetMetadata(ProjectElement.HintPath);

                        if(!Path.IsPathRooted(hintPath) &&
                          hintPath.IndexOf("$(", StringComparison.Ordinal) == -1)
                            buildItem.SetMetadata(ProjectElement.HintPath, Path.Combine(projectFolder,
                                hintPath));
                    }

                    referenceDictionary.Add(reference.Include, buildItem);
                }

            // Convert project references to regular references that point to the output assembly.
            // Project references get built and we may not have enough info for that to happen
            // successfully.  As such, we'll assume the project has already been built and that its
            // target exists.
            foreach(BuildItem reference in project.MSBuildProject.GetEvaluatedItemsByName("ProjectReference"))
            {
                projRef = new MSBuildProject(reference.Include);
                projRef.SetConfiguration(project.Configuration, project.Platform, project.MSBuildOutDir);

                buildItem = projRef.ProjectFile.AddNewItem("Reference",
                    Path.GetFileNameWithoutExtension(projRef.AssemblyName));
                buildItem.SetMetadata("HintPath", projRef.AssemblyName);
                referenceDictionary.Add(buildItem.Include, buildItem);
            }

            // For each source, make three passes: one for projects, one for assemblies and one
            // for comments files.  Projects and comments files are optional but when all done,
            // at least one assembly must have been found.
            foreach(DocumentationSource ds in project.DocumentationSources)
            {
                fileCount = 0;

                this.ReportProgress("Source: {0}", ds.SourceFile);

                foreach(var sourceProject in DocumentationSource.Projects(ds.SourceFile, ds.IncludeSubFolders,
                  !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration,
                  !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform))
                {
                    // NOTE: This code in EntityReferenceWindow.IndexComments should be similar to this!

                    // Solutions are followed by the projects that they contain
                    if(sourceProject.ProjectFileName.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
                    {
                        lastSolution = sourceProject.ProjectFileName;
                        continue;
                    }

                    if(!projectDictionary.ContainsKey(sourceProject.ProjectFileName))
                    {
                        // These are handled below
                        this.ReportProgress("    Found project '{0}'", sourceProject.ProjectFileName);

                        projRef = new MSBuildProject(sourceProject.ProjectFileName);

                        // Use the project file configuration and platform properties if they are set.  If not,
                        // use the documentation source values.  If they are not set, use the SHFB project settings.
                        projRef.SetConfiguration(
                            !String.IsNullOrEmpty(sourceProject.Configuration) ? sourceProject.Configuration :
                                !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration,
                            !String.IsNullOrEmpty(sourceProject.Platform) ? sourceProject.Platform :
                                !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform,
                            project.MSBuildOutDir);

                        // Add Visual Studio solution macros if necessary
                        if(lastSolution != null)
                            projRef.SetSolutionMacros(lastSolution);

                        projectDictionary.Add(sourceProject.ProjectFileName, projRef);
                    }
                    else
                        this.ReportProgress("    Ignoring duplicate project file '{0}'", sourceProject.ProjectFileName);

                    fileCount++;
                }

                foreach(string asmName in DocumentationSource.Assemblies(ds.SourceFile, ds.IncludeSubFolders))
                {
                    if(!assembliesList.Contains(asmName))
                    {
                        // Assemblies are parsed in place by MRefBuilder so we
                        // don't have to do anything with them here.
                        this.ReportProgress("    Found assembly '{0}'", asmName);
                        assembliesList.Add(asmName);
                    }
                    else
                        this.ReportProgress("    Ignoring duplicate assembly file '{0}'", asmName);

                    fileCount++;
                }

                foreach(string commentsName in DocumentationSource.CommentsFiles(
                  ds.SourceFile, ds.IncludeSubFolders))
                {
                    if(!commentsList.Contains(commentsName))
                    {
                        // These are handled below
                        commentsList.Add(commentsName);
                    }
                    else
                        this.ReportProgress("    Ignoring duplicate comments file '{0}'", commentsName);

                    fileCount++;
                }

                if(fileCount == 0)
                    this.ReportWarning("BE0006", "Unable to location any " +
                        "documentation sources for '{0}'", ds.SourceFile);
            }

            // Parse projects for assembly, comments, and reference info
            if(projectDictionary.Count != 0)
            {
                this.ReportProgress("\r\nParsing project files");

                foreach(MSBuildProject msbProject in projectDictionary.Values)
                {
                    workingPath = msbProject.AssemblyName;

                    if(!String.IsNullOrEmpty(workingPath))
                    {
                        if(!File.Exists(workingPath))
                            throw new BuilderException("BE0040", "Project assembly does not exist: " + workingPath);

                        this.ReportProgress("    Found assembly '{0}'", workingPath);
                        assembliesList.Add(workingPath);
                    }
                    else
                        throw new BuilderException("BE0067", String.Format(CultureInfo.InvariantCulture,
                            "Unable to obtain assembly name from project file '{0}' using Configuration " +
                            "'{1}', Platform '{2}'", msbProject.ProjectFile.FullFileName,
                            msbProject.ProjectFile.GetEvaluatedProperty(ProjectElement.Configuration),
                            msbProject.ProjectFile.GetEvaluatedProperty(ProjectElement.Platform)));

                    workingPath = msbProject.XmlCommentsFile;

                    if(!String.IsNullOrEmpty(workingPath))
                    {
                        if(!File.Exists(workingPath))
                            throw new BuilderException("BE0041",
                                "Project XML comments file does not exist: " + workingPath);

                        commentsList.Add(workingPath);
                    }

                    // Note the highest framework version used
                    projFramework = msbProject.TargetFrameworkVersion;

                    if(!String.IsNullOrEmpty(projFramework))
                    {
                        projFramework = FrameworkVersionTypeConverter.LatestMatching(projFramework.Substring(1));

                        if(String.Compare(targetFramework, projFramework, StringComparison.OrdinalIgnoreCase) < 0)
                            targetFramework = projFramework;
                    }

                    // Clone the project's references
                    msbProject.CloneReferences(referenceDictionary);
                }
            }

            if(project.FrameworkVersion != targetFramework)
            {
                this.ReportWarning("BE0007", "A project with a higher " +
                    "framework version was found.  Changing project " +
                    "FrameworkVersion property from '{0}' to '{1}' for " +
                    "the build", project.FrameworkVersion, targetFramework);

                project.FrameworkVersion = targetFramework;
            }

            if(assembliesList.Count == 0)
                throw new BuilderException("BE0042", "You must specify at " +
                    "least one documentation source in the form of an " +
                    "assembly or a Visual Studio solution/project file");

            // Log the references found, if any
            if(referenceDictionary.Count != 0)
            {
                this.ReportProgress("\r\nReferences to use:");

                string[] keys = new string[referenceDictionary.Keys.Count];
                referenceDictionary.Keys.CopyTo(keys, 0);
                Array.Sort(keys);

                foreach(string key in keys)
                    this.ReportProgress("    {0}", key);
            }

            if(commentsList.Count != 0)
                this.ReportProgress("\r\nCopying XML comments files");

            // XML comments files are copied to the working folder in case they
            // need to be fixed up.
            foreach(string commentsName in commentsList)
            {
                workingPath = workingFolder + Path.GetFileName(commentsName);

                // Warn if there is a duplicate and copy the comments file
                // to a unique name to preserve its content.
                if(File.Exists(workingPath))
                {
                    workingPath = workingFolder + Guid.NewGuid().ToString("B");

                    this.ReportWarning("BE0063", "'{0}' matches a " +
                        "previously copied comments filename.  The " +
                        "duplicate will be copied to a unique name to " +
                        "preserve the comments it contains.", commentsName);
                }

                try
                {
                    // Not all XML files found may be comments files.  Ignore
                    // those that are not.
                    testComments = new XPathDocument(commentsName);
                    navComments = testComments.CreateNavigator();

                    if(navComments.SelectSingleNode("doc/members") == null)
                    {
                        this.ReportWarning("BE0005", "File '{0}' does not " +
                            "contain a 'doc/members' node and will not be " +
                            "used as an XML comments file.",
                            commentsName);
                        continue;
                    }
                }
                catch(Exception ex)
                {
                    this.ReportWarning("BE0061", "File '{0}' could not be " +
                        "loaded and will not be used as an XML comments file." +
                        "  Error: {1}", commentsName, ex.Message);
                    continue;
                }

                File.Copy(commentsName, workingPath, true);
                File.SetAttributes(workingPath, FileAttributes.Normal);

                // Add the file to the XML comments file collection
                comments = new XmlCommentsFile(workingPath);

                // Fixup comments for CPP comments files?
                if(project.CppCommentsFixup)
                    comments.FixupComments();

                commentsFiles.Add(comments);

                this.ReportProgress("    {0} -> {1}", commentsName,
                    workingPath);
            }

            if(commentsFiles.Count == 0)
                this.ReportWarning("BE0062", "No XML comments files found.  " +
                    "The help file will not contain any member comments.");

            this.ExecutePlugIns(ExecutionBehaviors.After);
        }
Beispiel #4
0
        //=====================================================================

        /// <summary>
        /// Validate the documentation source information and copy the files to the working folder
        /// </summary>
        /// <exception cref="BuilderException">This is thrown if any of the information is invalid</exception>
        private void ValidateDocumentationSources()
        {
            List<string> commentsList = new List<string>();
            Dictionary<string, MSBuildProject> projectDictionary = new Dictionary<string, MSBuildProject>();
            HashSet<string> targetFrameworksSeen = new HashSet<string>(),
                targetFrameworkVersionsSeen = new HashSet<string>();

            MSBuildProject projRef;
            XPathDocument testComments;
            XPathNavigator navComments;
            int fileCount;
            string workingPath, lastSolution = null;

            this.ReportProgress(BuildStep.ValidatingDocumentationSources,
                "Validating and copying documentation source information");

            assembliesList = new Collection<string>();
            referenceDictionary = new Dictionary<string, Tuple<string, string, List<KeyValuePair<string, string>>>>();
            commentsFiles = new XmlCommentsFileCollection();

            if(this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                return;

            // It's possible a plug-in might want to add or remove assemblies so we'll run them before checking
            // to see if the project has any.
            this.ExecutePlugIns(ExecutionBehaviors.Before);

            if(project.DocumentationSources.Count() == 0)
                throw new BuilderException("BE0039", "The project does not have any documentation sources defined");

            // Clone the project's references.  These will be added to a build project later on so we'll note the
            // necessary information needed to create the reference in the future project.
            foreach(string refType in (new string[] { "Reference", "COMReference" }))
                foreach(ProjectItem reference in project.MSBuildProject.GetItems(refType))
                    referenceDictionary.Add(reference.EvaluatedInclude, Tuple.Create(reference.ItemType,
                        reference.EvaluatedInclude, reference.Metadata.Select(m =>
                            new KeyValuePair<string, string>(m.Name, m.EvaluatedValue)).ToList()));

            // Convert project references to regular references that point to the output assembly.  Project
            // references get built and we may not have enough info for that to happen successfully.  As such,
            // we'll assume the project has already been built and that its target exists.
            foreach(ProjectItem reference in project.MSBuildProject.GetItems("ProjectReference"))
            {
                // Ignore references used only for MSBuild dependency determination
                var refOutput = reference.GetMetadata(BuildItemMetadata.ReferenceOutputAssembly);

                if(refOutput != null && refOutput.EvaluatedValue.Equals("false", StringComparison.OrdinalIgnoreCase))
                {
                    this.ReportProgress("Ignoring reference to '{0}' which is only used for MSBuild dependency " +
                        "determination", reference.EvaluatedInclude);
                    continue;
                }

                using(projRef = new MSBuildProject(reference.EvaluatedInclude))
                {
                    // .NET 4.5 supports a property that tells MSBuild to put the project output into a
                    // project-specific folder in OutDir.
                    var projectSpecificFolder = project.MSBuildProject.AllEvaluatedProperties.FirstOrDefault(
                        p => p.Name == "GenerateProjectSpecificOutputFolder");

                    bool usesProjectSpecificOutput = (projectSpecificFolder != null &&
                      !String.IsNullOrWhiteSpace(projectSpecificFolder.EvaluatedValue) &&
                      Convert.ToBoolean(projectSpecificFolder.EvaluatedValue, CultureInfo.InvariantCulture));

                    projRef.SetConfiguration(project.Configuration, project.Platform, project.MSBuildOutDir,
                        usesProjectSpecificOutput);

                    referenceDictionary.Add(projRef.AssemblyName, Tuple.Create("Reference",
                        Path.GetFileNameWithoutExtension(projRef.AssemblyName),
                        (new [] { new KeyValuePair<string, string>("HintPath", projRef.AssemblyName) }).ToList()));
                }
            }

            try
            {
                // For each source, make three passes: one for projects, one for assemblies and one for comments
                // files.  Projects and comments files are optional but when all done, at least one assembly must
                // have been found.
                foreach(DocumentationSource ds in project.DocumentationSources)
                {
                    fileCount = 0;

                    this.ReportProgress("Source: {0}", ds.SourceFile);

                    foreach(var sourceProject in ds.Projects(
                      !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration,
                      !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform))
                    {
                        // NOTE: This code in EntityReferenceWindow.IndexComments should be similar to this!

                        // Solutions are followed by the projects that they contain
                        if(sourceProject.ProjectFileName.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
                        {
                            lastSolution = sourceProject.ProjectFileName;
                            continue;
                        }

                        if(!projectDictionary.ContainsKey(sourceProject.ProjectFileName))
                        {
                            // These are handled below
                            this.ReportProgress("    Found project '{0}'", sourceProject.ProjectFileName);

                            // .NET 4.5 supports a property that tells MSBuild to put the project output into a
                            // project-specific folder in OutDir.
                            var projectSpecificFolder = project.MSBuildProject.AllEvaluatedProperties.FirstOrDefault(
                                p => p.Name == "GenerateProjectSpecificOutputFolder");

                            bool usesProjectSpecificOutput = (projectSpecificFolder != null &&
                              !String.IsNullOrWhiteSpace(projectSpecificFolder.EvaluatedValue) &&
                              Convert.ToBoolean(projectSpecificFolder.EvaluatedValue, CultureInfo.InvariantCulture));

                            projRef = new MSBuildProject(sourceProject.ProjectFileName);

                            // Use the project file configuration and platform properties if they are set.  If not,
                            // use the documentation source values.  If they are not set, use the SHFB project settings.
                            projRef.SetConfiguration(
                                !String.IsNullOrEmpty(sourceProject.Configuration) ? sourceProject.Configuration :
                                    !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration,
                                !String.IsNullOrEmpty(sourceProject.Platform) ? sourceProject.Platform :
                                    !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform,
                                project.MSBuildOutDir, usesProjectSpecificOutput);

                            // Add Visual Studio solution macros if necessary
                            if(lastSolution != null)
                                projRef.SetSolutionMacros(lastSolution);

                            projectDictionary.Add(sourceProject.ProjectFileName, projRef);
                        }
                        else
                            this.ReportProgress("    Ignoring duplicate project file '{0}'", sourceProject.ProjectFileName);

                        fileCount++;
                    }

                    foreach(string asmName in ds.Assemblies)
                    {
                        if(!assembliesList.Contains(asmName))
                        {
                            // Assemblies are parsed in place by MRefBuilder so we don't have to do anything with
                            // them here.
                            this.ReportProgress("    Found assembly '{0}'", asmName);
                            assembliesList.Add(asmName);
                        }
                        else
                            this.ReportProgress("    Ignoring duplicate assembly file '{0}'", asmName);

                        fileCount++;
                    }

                    foreach(string commentsName in ds.CommentsFiles)
                    {
                        if(!commentsList.Contains(commentsName))
                        {
                            // These are handled below
                            commentsList.Add(commentsName);
                        }
                        else
                            this.ReportProgress("    Ignoring duplicate comments file '{0}'", commentsName);

                        fileCount++;
                    }

                    if(fileCount == 0)
                        this.ReportWarning("BE0006", "Unable to locate any documentation sources for '{0}' " +
                            "(Configuration: {1} Platform: {2})", ds.SourceFile,
                            !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration,
                            !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform);
                }

                // Parse projects for assembly, comments, and reference info
                if(projectDictionary.Count != 0)
                {
                    this.ReportProgress("\r\nParsing project files");

                    foreach(MSBuildProject msbProject in projectDictionary.Values)
                    {
                        workingPath = msbProject.AssemblyName;

                        if(!String.IsNullOrEmpty(workingPath))
                        {
                            if(!File.Exists(workingPath))
                                throw new BuilderException("BE0040", "Project assembly does not exist: " + workingPath);

                            if(!assembliesList.Contains(workingPath))
                            {
                                // Assemblies are parsed in place by MRefBuilder so we don't have to do anything
                                // with them here.
                                this.ReportProgress("    Found assembly '{0}'", workingPath);
                                assembliesList.Add(workingPath);
                            }
                            else
                                this.ReportProgress("    Ignoring duplicate assembly file '{0}'", workingPath);
                        }
                        else
                            throw new BuilderException("BE0067", String.Format(CultureInfo.CurrentCulture,
                                "Unable to obtain assembly name from project file '{0}' using Configuration " +
                                "'{1}', Platform '{2}'", msbProject.ProjectFile.FullPath,
                                msbProject.ProjectFile.AllEvaluatedProperties.Last(
                                    p => p.Name == BuildItemMetadata.Configuration).EvaluatedValue,
                                msbProject.ProjectFile.AllEvaluatedProperties.Last(
                                    p => p.Name == BuildItemMetadata.Platform).EvaluatedValue));

                        workingPath = msbProject.XmlCommentsFile;

                        if(!String.IsNullOrEmpty(workingPath))
                        {
                            if(!File.Exists(workingPath))
                                throw new BuilderException("BE0041",
                                    "Project XML comments file does not exist: " + workingPath);

                            if(!commentsList.Contains(workingPath))
                            {
                                // These are handled below
                                commentsList.Add(workingPath);
                            }
                            else
                                this.ReportProgress("    Ignoring duplicate comments file '{0}'", workingPath);
                        }

                        // Note the platforms seen and the highest framework version used
                        targetFrameworksSeen.Add(msbProject.TargetFrameworkIdentifier);
                        targetFrameworkVersionsSeen.Add(msbProject.TargetFrameworkVersion);

                        // Clone the project's reference information
                        msbProject.CloneReferenceInfo(referenceDictionary);
                    }

                    // If we saw multiple framework types in the projects, stop now.  Due to the different
                    // assemblies used, we cannot mix the project types within the same SHFB project.  They will
                    // need to be documented separately and can be merged using the Version Builder plug-in if
                    // needed.
                    if(targetFrameworksSeen.Count > 1)
                        throw new BuilderException("BE0070", "Differing framework types were detected in the " +
                            "documentation sources (i.e. .NET, Silverlight, Portable).  Due to the different " +
                            "sets of assemblies used, the different frameworks cannot be mixed within the same " +
                            "documentation project.  See the error number topic in the help file for details.");

                    // If a project with a higher framework version was found, switch to that version now
                    var projectFramework = reflectionDataDictionary.CoreFrameworkMatching(
                        targetFrameworksSeen.First(), new Version(targetFrameworkVersionsSeen.Max(f => f)), true);

                    if(frameworkReflectionData != projectFramework)
                    {
                        // If redirected and no suitable version was found, we can't go any further
                        if(projectFramework == null)
                            throw new BuilderException("BE0073", String.Format(CultureInfo.CurrentCulture,
                                "A project with a different or higher framework version was found but that " +
                                "version ({0} {1}) or a suitable redirected version was not found on this " +
                                "system.  The build cannot continue.", targetFrameworksSeen.First(),
                                targetFrameworkVersionsSeen.Max(f => f)));

                        this.ReportWarning("BE0007", "A project with a different or higher framework version " +
                            "was found.  Changing project FrameworkVersion property from '{0}' to '{1}' for " +
                            "the build.", project.FrameworkVersion, projectFramework.Title);

                        project.FrameworkVersion = projectFramework.Title;
                        frameworkReflectionData = projectFramework;
                    }
                }
            }
            finally
            {
                // Dispose of any MSBuild projects that we loaded
                foreach(var p in projectDictionary.Values)
                    p.Dispose();
            }

            if(assembliesList.Count == 0)
                throw new BuilderException("BE0042", "You must specify at least one documentation source in " +
                    "the form of an assembly or a Visual Studio solution/project file");

            // Log the references found, if any
            if(referenceDictionary.Count != 0)
            {
                this.ReportProgress("\r\nReferences to include (excluding framework assemblies):");

                string[] keys = new string[referenceDictionary.Keys.Count];
                referenceDictionary.Keys.CopyTo(keys, 0);
                Array.Sort(keys);

                // Filter out references related to the framework.  MRefBuilder will resolve these
                // automatically.
                foreach(string key in keys)
                    if(frameworkReflectionData.ContainsAssembly(key))
                        referenceDictionary.Remove(key);
                    else
                        this.ReportProgress("    {0}", key);

                if(referenceDictionary.Count == 0)
                    this.ReportProgress("    None");
            }

            if(commentsList.Count != 0)
                this.ReportProgress("\r\nCopying XML comments files");

            // XML comments files are copied to the working folder in case they need to be fixed up
            foreach(string commentsName in commentsList)
            {
                workingPath = workingFolder + Path.GetFileName(commentsName);

                // Warn if there is a duplicate and copy the comments file to a unique name to preserve its
                // content.
                if(File.Exists(workingPath))
                {
                    workingPath = workingFolder + Guid.NewGuid().ToString("B");

                    this.ReportWarning("BE0063", "'{0}' matches a previously copied comments filename.  The " +
                        "duplicate will be copied to a unique name to preserve the comments it contains.",
                        commentsName);
                }

                try
                {
                    // Not all XML files found may be comments files.  Ignore those that are not.
                    testComments = new XPathDocument(commentsName);
                    navComments = testComments.CreateNavigator();

                    if(navComments.SelectSingleNode("doc/members") == null)
                    {
                        this.ReportWarning("BE0005", "File '{0}' does not contain a 'doc/members' node and " +
                            "will not be used as an XML comments file.", commentsName);
                        continue;
                    }
                }
                catch(Exception ex)
                {
                    this.ReportWarning("BE0061", "File '{0}' could not be loaded and will not be used as an " +
                        "XML comments file.  Error: {1}", commentsName, ex.Message);
                    continue;
                }

                File.Copy(commentsName, workingPath, true);
                File.SetAttributes(workingPath, FileAttributes.Normal);

                // Add the file to the XML comments file collection
                commentsFiles.Add(new XmlCommentsFile(workingPath));

                this.ReportProgress("    {0} -> {1}", commentsName, workingPath);
            }

            if(commentsFiles.Count == 0)
                this.ReportWarning("BE0062", "No XML comments files found.  The help file will not contain " +
                    "any member comments.");

            this.ExecutePlugIns(ExecutionBehaviors.After);
        }
        /// <summary>
        /// This is the thread method that indexes the comments files
        /// </summary>
        /// <remarks>Rather than a partial build, we'll just index the
        /// comments files.</remarks>
        private void IndexComments()
        {
            HashSet<string> projectDictionary = new HashSet<string>();
            Collection<string> frameworkLocations = new Collection<string>();
            Dictionary<string, string> cacheName = new Dictionary<string,string>();
            IndexedCommentsCache cache = new IndexedCommentsCache(100);
            MSBuildProject projRef;
            string path, lastSolution = null;

            try
            {
                BuildProcess.GetFrameworkCommentsFiles(frameworkLocations,
                    cacheName, currentProject.Language,
                    currentProject.FrameworkVersion);

                // Index the framework comments
                foreach(string location in frameworkLocations)
                {
                    path = Environment.ExpandEnvironmentVariables(location);
                    cache.IndexCommentsFiles(path, null, true, null);
                }

                // Index the comments file documentation sources
                foreach(string file in currentProject.DocumentationSources.CommentsFiles)
                    cache.IndexCommentsFiles(Path.GetDirectoryName(file),
                        Path.GetFileName(file), false, null);

                // Also, index the comments files in project documentation sources
                foreach(DocumentationSource ds in currentProject.DocumentationSources)
                    foreach(var sourceProject in DocumentationSource.Projects(ds.SourceFile, ds.IncludeSubFolders,
                      !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : currentProject.Configuration,
                      !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : currentProject.Platform))
                    {
                        // NOTE: This code should be similar to the code in BuildProcess.ValidateDocumentationSources!

                        // Solutions are followed by the projects that they contain
                        if(sourceProject.ProjectFileName.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
                        {
                            lastSolution = sourceProject.ProjectFileName;
                            continue;
                        }

                        // Ignore projects that we've already seen
                        if(projectDictionary.Add(sourceProject.ProjectFileName))
                        {
                            projRef = new MSBuildProject(sourceProject.ProjectFileName);

                            // Use the project file configuration and platform properties if they are set.  If not,
                            // use the documentation source values.  If they are not set, use the SHFB project settings.
                            projRef.SetConfiguration(
                                !String.IsNullOrEmpty(sourceProject.Configuration) ? sourceProject.Configuration :
                                    !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : currentProject.Configuration,
                                !String.IsNullOrEmpty(sourceProject.Platform) ? sourceProject.Platform :
                                    !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : currentProject.Platform,
                                currentProject.MSBuildOutDir);

                            // Add Visual Studio solution macros if necessary
                            if(lastSolution != null)
                                projRef.SetSolutionMacros(lastSolution);

                            if(!String.IsNullOrEmpty(projRef.XmlCommentsFile))
                                cache.IndexCommentsFiles(Path.GetDirectoryName(projRef.XmlCommentsFile),
                                    Path.GetFileName(projRef.XmlCommentsFile), false, null);
                        }
                    }

                this.Invoke(new IndexingCompleted(this.Completed), new object[] { cache });
            }
            catch(Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
                this.Invoke(new IndexingFailed(this.Failed), new object[] { ex.Message });
            }
        }