Exemple #1
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;
            XmlCommentsFile comments;
            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(ProjectElement.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))
                {
                    projRef.SetConfiguration(project.Configuration, project.Platform, project.MSBuildOutDir);

                    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 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 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 == ProjectElement.Configuration).EvaluatedValue,
                                msbProject.ProjectFile.AllEvaluatedProperties.Last(
                                    p => p.Name == ProjectElement.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 = FrameworkDictionary.AllFrameworks.FrameworkMatching(
                        targetFrameworksSeen.First(), new Version(targetFrameworkVersionsSeen.Max(f => f)), true);

                    if(frameworkSettings != 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;
                        frameworkSettings = 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(frameworkSettings.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
                comments = new XmlCommentsFile(workingPath);

                // Fix up 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);
        }
        /// <summary>
        /// This method is used to execute the plug-in during the build process
        /// </summary>
        /// <param name="context">The current execution context</param>
        public void Execute(Utils.PlugIn.ExecutionContext context)
        {
            Encoding enc = Encoding.Default;
            Thread browserThread;
            XmlDocument sharedContent;
            XPathNavigator navContent, item;
            XmlCommentsFile comments;
            string sharedContentFilename, workingPath, content;

            // Copy any XML comments files from the project to the working
            // folder.  Solutions, projects, and assemblies are ignored as
            // they won't be used with this build type.
            if(context.BuildStep == BuildStep.ValidatingDocumentationSources)
            {
                builder.ExecuteBeforeStepPlugIns();

                foreach(DocumentationSource ds in
                  builder.CurrentProject.DocumentationSources)
                    foreach(string commentsName in
                      DocumentationSource.CommentsFiles(ds.SourceFile,
                      ds.IncludeSubFolders))
                    {
                        workingPath = builder.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 = builder.WorkingFolder +
                                Guid.NewGuid().ToString("B");

                            builder.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);
                        }

                        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(builder.CurrentProject.CppCommentsFixup)
                            comments.FixupComments();

                        builder.CommentsFiles.Add(comments);
                        builder.ReportProgress("    {0} -> {1}", commentsName,
                            workingPath);
                    }

                builder.ExecuteAfterStepPlugIns();
                return;
            }

            // Remove the version information items from the shared content
            // file as the AjaxDoc reflection file doesn't contain version
            // information.
            if(context.BuildStep == BuildStep.GenerateSharedContent)
            {
                builder.ReportProgress("Removing version information items " +
                    "from shared content file");

                sharedContentFilename = builder.WorkingFolder +
                    "SharedBuilderContent.xml";

                sharedContent = new XmlDocument();
                sharedContent.Load(sharedContentFilename);
                navContent = sharedContent.CreateNavigator();

                item = navContent.SelectSingleNode("content/item[@id='" +
                    "locationInformation']");

                if(item != null)
                    item.DeleteSelf();

                item = navContent.SelectSingleNode("content/item[@id='" +
                    "assemblyNameAndModule']");

                if(item != null)
                    item.DeleteSelf();

                sharedContent.Save(sharedContentFilename);
                return;
            }

            builder.ReportProgress("Using project '{0}'", projectName);

            if(regenerateFiles)
            {
                // Regenerate the files first.  This is done by starting a
                // thread to invoke the AjaxDoc application via a web browser
                // control.  This is necessary as the brower control needs to
                // run in a thread with a single-threaded apartment state.
                // We can't just request the page as AjaxDoc has to post back
                // to itself in order to store the generated information.
                builder.ReportProgress("Generating XML comments and " +
                    "reflection information via AjaxDoc");

                browserThread = new Thread(RunBrowser);
                browserThread.SetApartmentState(ApartmentState.STA);

                navCount = 0;
                browserThread.Start();

                if(!browserThread.Join(11000))
                    browserThread.Abort();

                if(!String.IsNullOrEmpty(errorText))
                    throw new BuilderException("ADP0003", "AjaxDoc encountered " +
                        "a scripting error: " + errorText);

                if(commentsFile == null || reflectionFile == null)
                    throw new BuilderException("ADP0004", "Unable to produce " +
                        "comments file and/or reflection file");

                builder.ReportProgress("Generated comments file '{0}' " +
                    "and reflection file '{1}'", commentsFile, reflectionFile);
            }
            else
            {
                // Use the existing files
                commentsFile = "Output/" + projectName + ".xml";
                reflectionFile = "Output/" + projectName + ".org";

                builder.ReportProgress("Using existing XML comments file " +
                    "'{0}' and reflection information file '{1}'",
                    commentsFile, reflectionFile);
            }

            // Allow Before step plug-ins to run
            builder.ExecuteBeforeStepPlugIns();

            // Download the files
            using(WebClient webClient = new WebClient())
            {
                webClient.UseDefaultCredentials = userCreds.UseDefaultCredentials;
                if(!userCreds.UseDefaultCredentials)
                    webClient.Credentials = new NetworkCredential(
                        userCreds.UserName, userCreds.Password);

                webClient.CachePolicy = new RequestCachePolicy(
                    RequestCacheLevel.NoCacheNoStore);

                if(proxyCreds.UseProxyServer)
                {
                    webClient.Proxy = new WebProxy(proxyCreds.ProxyServer, true);

                    if(!proxyCreds.Credentials.UseDefaultCredentials)
                        webClient.Proxy.Credentials = new NetworkCredential(
                            proxyCreds.Credentials.UserName,
                            proxyCreds.Credentials.Password);
                }

                // Since there are only two, download them synchronously
                workingPath = builder.WorkingFolder + projectName + ".xml";
                builder.ReportProgress("Downloading {0}", commentsFile);
                webClient.DownloadFile(ajaxDocUrl + commentsFile, workingPath);
                builder.CommentsFiles.Add(new XmlCommentsFile(workingPath));

                builder.ReportProgress("Downloading {0}", reflectionFile);
                webClient.DownloadFile(ajaxDocUrl + reflectionFile,
                    builder.ReflectionInfoFilename);

                builder.ReportProgress("Downloads completed successfully");
            }

            // AjaxDoc 1.1 prefixes all member names with "J#" which causes
            // BuildAssembler's ResolveReferenceLinksComponent2 component in
            // the Sept 2007 CTP to crash.  As such, we'll strip it out.  I
            // can't see a need for it anyway.
            content = BuildProcess.ReadWithEncoding(workingPath, ref enc);
            content = content.Replace(":J#", ":");

            using(StreamWriter sw = new StreamWriter(workingPath, false, enc))
            {
                sw.Write(content);
            }

            content = BuildProcess.ReadWithEncoding(
                builder.ReflectionInfoFilename, ref enc);
            content = content.Replace(":J#", ":");

            using(StreamWriter sw = new StreamWriter(
              builder.ReflectionInfoFilename, false, enc))
            {
                sw.Write(content);
            }

            // Don't apply the filter in a partial build
            if(!builder.IsPartialBuild && builder.BuildApiFilter.Count != 0)
            {
                builder.ReportProgress("Applying API filter manually");
                builder.ApplyManualApiFilter(builder.BuildApiFilter,
                    builder.ReflectionInfoFilename);
            }

            // Allow After step plug-ins to run
            builder.ExecuteAfterStepPlugIns();
        }
        //=====================================================================
        /// <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);
        }