예제 #1
0
        //=====================================================================

        /// <summary>
        /// Call this method to perform the build on the project.
        /// </summary>
        /// <event cref="BuildStepChanged">This event fires when the current build step changes.</event>
        /// <event cref="BuildProgress">This event fires to report progress information.</event>
        public void Build()
        {
            Project msBuildProject = null;
            ProjectItem projectItem;
            Version version;
            string resolvedPath, helpFile, languageFile, scriptFile, hintPath, message = null;
            SandcastleProject originalProject = null;
            int waitCount;

            System.Diagnostics.Debug.WriteLine("Build process starting\r\n");

            try
            {
                // If the project isn't using final values suitable for the build, create a copy of the
                // project that is using final values.
                if(!project.UsingFinalValues)
                {
                    originalProject = project;
                    project = new SandcastleProject(originalProject);
                }

                Assembly asm = Assembly.GetExecutingAssembly();

                FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(asm.Location);
                this.ReportProgress(BuildStep.Initializing, "[{0}, version {1}]", fvi.ProductName, fvi.ProductVersion);

                buildStart = stepStart = DateTime.Now;

                msBuildExePath = Path.Combine(ProjectCollection.GlobalProjectCollection.Toolsets.First(
                    t => t.ToolsVersion == project.MSBuildProject.ToolsVersion).ToolsPath, "MSBuild.exe");

                // Get the location of the template files
                templateFolder = ComponentUtilities.ToolsFolder + @"Templates\";

                // Make sure we start out in the project's output folder in case the output folder is relative
                // to it.
                projectFolder = Path.GetDirectoryName(originalProjectName);

                if(projectFolder.Length == 0)
                    projectFolder = Directory.GetCurrentDirectory();

                projectFolder += @"\";

                Directory.SetCurrentDirectory(projectFolder);

                this.ReportProgress("Creating output and working folders...");

                outputFolder = project.OutputPath;

                if(String.IsNullOrEmpty(outputFolder))
                    outputFolder = Directory.GetCurrentDirectory();
                else
                    outputFolder = Path.GetFullPath(outputFolder);

                if(!Directory.Exists(outputFolder))
                    Directory.CreateDirectory(outputFolder);

                if(outputFolder[outputFolder.Length - 1] != '\\')
                    outputFolder += @"\";

                // Create the log file.  The log may be in a folder other than the output so make sure it exists
                // too.
                if(!Directory.Exists(Path.GetDirectoryName(this.LogFilename)))
                    Directory.CreateDirectory(Path.GetDirectoryName(this.LogFilename));

                swLog = new StreamWriter(this.LogFilename);

                swLog.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<shfbBuild product=\"{0}\" " +
                    "version=\"{1}\" projectFile=\"{2}\" started=\"{3}\">\r\n<buildStep step=\"{4}\">",
                    fvi.ProductName, fvi.ProductVersion, originalProjectName, DateTime.Now,
                    BuildStep.Initializing);

                if(project.WorkingPath.Path.Length == 0)
                    workingFolder = outputFolder + @"Working\";
                else
                    workingFolder = project.WorkingPath;

                if((project.HelpFileFormat & HelpFileFormats.Website) != 0)
                    BuildProcess.VerifySafePath("OutputPath", outputFolder, projectFolder);

                // The output folder and the working folder cannot be the same
                if(workingFolder == outputFolder)
                    throw new BuilderException("BE0030", "The OutputPath and WorkingPath properties cannot be " +
                        "set to the same path");

                // For MS Help 2, the HTML Help Name cannot contain spaces
                if((project.HelpFileFormat & HelpFileFormats.MSHelp2) != 0 && this.ResolvedHtmlHelpName.IndexOf(' ') != -1)
                    throw new BuilderException("BE0031", "For MS Help 2 builds, the HtmlHelpName property " +
                        "cannot contain spaces as they are not valid in the collection name.");

                // For MS Help Viewer, the HTML Help Name cannot contain periods, ampersands, or pound signs
                if((project.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0 &&
                  this.ResolvedHtmlHelpName.IndexOfAny(new[] { '.', '#', '&' }) != -1)
                    throw new BuilderException("BE0075", "For MS Help Viewer builds, the HtmlHelpName property " +
                        "cannot contain periods, ampersands, or pound signs as they are not valid in the " +
                        "help file name.");

                // Make sure we can find the tools
                this.FindTools();

                // Check for the SHFBROOT environment variable.  It may not be present yet if a reboot hasn't
                // occurred after installation.  In such cases, set it to the proper folder for this process so
                // that projects can be loaded and built.
                if(Environment.GetEnvironmentVariable("SHFBROOT") == null)
                {
                    // We won't issue a warning since it may not be defined in some build environments such as
                    // on a build server.  In such cases, it is passed in as a command line option to MSBuild.
                    // Storing it in the environment here lets the SHFB build projects work as expected.
                    this.ReportProgress("The SHFBROOT system environment variable was not found.  This " +
                        "variable is usually created during installation and may require a reboot.  It has " +
                        "been defined temporarily for this process as: SHFBROOT={0}",
                        ComponentUtilities.ToolsFolder);

                    Environment.SetEnvironmentVariable("SHFBROOT", ComponentUtilities.ToolsFolder);
                }

                // Get the framework settings to use for the build
                frameworkSettings = FrameworkDictionary.AllFrameworks.GetFrameworkWithRedirect(
                    project.FrameworkVersion);

                if(frameworkSettings == null)
                    throw new BuilderException("BE0071", String.Format(CultureInfo.CurrentCulture,
                        "Unable to locate information for the project framework version '{0}' or a suitable " +
                        "redirected version on this system.  See error number help topic for details.",
                        project.FrameworkVersion));

                if(!Directory.Exists(this.FrameworkReflectionDataFolder))
                    throw new BuilderException("BE0032", "Reflection data files for the selected framework " +
                        "do not exist yet (" + this.FrameworkReflectionDataFolder + ").  See help file for " +
                        "details about this error number.");

                // Warn if a different framework is being used for the build
                if(frameworkSettings.Title != project.FrameworkVersion)
                    this.ReportWarning("BE0072", "Project framework version '{0}' not found.  It has been " +
                        "redirected and will use '{1}' instead.", project.FrameworkVersion,
                        frameworkSettings.Title);

                // Get the composition container used to find build components in the rest of the build process
                this.ReportProgress("Locating components in the following folder(s):");

                if(!String.IsNullOrEmpty(project.ComponentPath))
                    this.ReportProgress("   {0}", project.ComponentPath);

                this.ReportProgress("   {0}", Path.GetDirectoryName(project.Filename));

                componentContainer = ComponentUtilities.CreateComponentContainer(new[] { project.ComponentPath,
                    Path.GetDirectoryName(project.Filename) });

                syntaxGenerators = componentContainer.GetExports<ISyntaxGeneratorFactory,
                    ISyntaxGeneratorMetadata>().Select(sf => sf.Metadata).ToList();
                buildComponents = componentContainer.GetExports<BuildComponentFactory,
                    IBuildComponentMetadata>().GroupBy(c => c.Metadata.Id).Select(g => g.First()).ToDictionary(
                        key => key.Metadata.Id, value => value.Value);

                // Figure out which presentation style to use
                var style = componentContainer.GetExports<PresentationStyleSettings,
                    IPresentationStyleMetadata>().FirstOrDefault(s => s.Metadata.Id.Equals(
                        project.PresentationStyle, StringComparison.OrdinalIgnoreCase));

                if(style == null)
                    throw new BuilderException("BE0001", "The PresentationStyle property value of '" +
                        project.PresentationStyle + "' is not recognized as a valid presentation style definition");

                presentationStyle = style.Value;

                this.ReportProgress("Using presentation style '{0}' located in '{1}'", style.Metadata.Id,
                    Path.Combine(presentationStyle.Location, presentationStyle.BasePath ?? String.Empty));

                var psErrors = presentationStyle.CheckForErrors();

                if(psErrors.Any())
                    throw new BuilderException("BE0004", String.Format(CultureInfo.CurrentCulture,
                        "The selected presentation style ({0}) is not valid.  Reason(s):\r\n{1}",
                        style.Metadata.Id, String.Join("\r\n", psErrors)));

                // If the presentation style does not support one or more of the selected help file formats,
                // stop now.
                if((project.HelpFileFormat & ~presentationStyle.SupportedFormats) != 0)
                    throw new BuilderException("BE0074", String.Format(CultureInfo.CurrentCulture,
                        "The selected presentation style ({0}) does not support one or more of the selected " +
                        "help file formats.  Supported formats: {1}", style.Metadata.Id,
                        presentationStyle.SupportedFormats));

                // Load the plug-ins if necessary
                if(project.PlugInConfigurations.Count != 0 || presentationStyle.PlugInDependencies.Count != 0)
                    this.LoadPlugIns();

                this.ExecutePlugIns(ExecutionBehaviors.After);

                try
                {
                    if(Directory.Exists(workingFolder))
                    {
                        // Clear any data from a prior run
                        this.ReportProgress(BuildStep.ClearWorkFolder, "Clearing working folder...");
                        BuildProcess.VerifySafePath("WorkingPath", workingFolder, projectFolder);

                        if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                        {
                            this.ExecutePlugIns(ExecutionBehaviors.Before);

                            try
                            {
                                Directory.Delete(workingFolder, true);
                            }
                            catch(IOException ioEx)
                            {
                                this.ReportProgress("    Not all prior output was removed from '{0}': {1}",
                                    workingFolder, ioEx.Message);
                            }
                            catch(UnauthorizedAccessException uaEx)
                            {
                                this.ReportProgress("    Not all prior output was removed from '{0}': {1}",
                                    workingFolder, uaEx.Message);
                            }

                            this.ExecutePlugIns(ExecutionBehaviors.After);
                        }
                    }

                    // If the help file is open, it will fail to build so try to get rid of it now before we
                    // get too far into it.
                    helpFile = outputFolder + this.ResolvedHtmlHelpName + ".chm";

                    if((project.HelpFileFormat & HelpFileFormats.HtmlHelp1) != 0 && File.Exists(helpFile))
                        File.Delete(helpFile);

                    helpFile = Path.ChangeExtension(helpFile, ".hxs");

                    if((project.HelpFileFormat & HelpFileFormats.MSHelp2) != 0 && File.Exists(helpFile))
                        File.Delete(helpFile);

                    helpFile = Path.ChangeExtension(helpFile, ".mshc");

                    if((project.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0 && File.Exists(helpFile))
                        File.Delete(helpFile);

                    if((project.HelpFileFormat & HelpFileFormats.Website) != 0)
                    {
                        helpFile = outputFolder + "Index.aspx";

                        if(File.Exists(helpFile))
                            File.Delete(helpFile);

                        helpFile = Path.ChangeExtension(helpFile, ".html");

                        if(File.Exists(helpFile))
                            File.Delete(helpFile);
                    }

                    helpFile = outputFolder + this.ResolvedHtmlHelpName + ".docx";

                    if((project.HelpFileFormat & HelpFileFormats.OpenXml) != 0 && File.Exists(helpFile))
                        File.Delete(helpFile);
                }
                catch(IOException ex)
                {
                    throw new BuilderException("BE0025", "Unable to remove prior build output: " + ex.Message);
                }
                catch
                {
                    throw;
                }

                Directory.CreateDirectory(workingFolder);

                // Make sure the HelpFileVersion property is in the form of a real version number
                try
                {
                    if(project.HelpFileVersion.IndexOf('{') == -1)
                        version = new Version(project.HelpFileVersion);
                    else
                        version = new Version(this.TransformText(project.HelpFileVersion));

                    if(version.Build == -1 || version.Revision == -1)
                        throw new FormatException("The version number must specify all four parts.  " +
                            "Specify zero for unused parts.");
                }
                catch(Exception ex)
                {
                    throw new BuilderException("BE0066", "The HelpFileVersion property value '" +
                        project.HelpFileVersion + "' is not in the correct format (#.#.#.#)", ex);
                }

                this.GarbageCollect();

                // Validate the documentation source information, gather assembly and reference info, and copy
                // XML comments files to the working folder.
                this.ValidateDocumentationSources();

                // Transform the shared builder content files
                language = project.Language;
                languageFile = Path.Combine(presentationStyle.ResolvePath(presentationStyle.ToolResourceItemsPath),
                    language.Name + ".xml");

                this.ReportProgress(BuildStep.GenerateSharedContent, "Generating shared content files ({0}, {1})...",
                    language.Name, language.DisplayName);

                if(!File.Exists(languageFile))
                {
                    languageFile = Path.Combine(presentationStyle.ResolvePath(presentationStyle.ToolResourceItemsPath),
                        "en-US.xml");

                    // Warn the user about the default being used
                    this.ReportWarning("BE0002", "Help file builder content for the '{0}, {1}' language could " +
                        "not be found.  Using 'en-US, English (US)' defaults.", language.Name, language.DisplayName);
                }

                // See if the user has translated the Sandcastle resources.  If not found, default to US English.
                languageFolder = Path.Combine(presentationStyle.ResolvePath(presentationStyle.ResourceItemsPath),
                    language.Name);

                if(Directory.Exists(languageFolder))
                    languageFolder = language.Name + @"\";
                else
                {
                    // Warn the user about the default being used.  The language will still be used for the help
                    // file though.
                    if(language.Name != "en-US")
                        this.ReportWarning("BE0003", "Sandcastle shared content for the '{0}, {1}' language " +
                            "could not be found.  Using 'en-US, English (US)' defaults.", language.Name,
                            language.DisplayName);

                    languageFolder = String.Empty;
                }

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    this.ExecutePlugIns(ExecutionBehaviors.Before);

                    this.TransformTemplate(Path.GetFileName(languageFile), Path.GetDirectoryName(languageFile),
                        workingFolder);
                    File.Move(workingFolder + Path.GetFileName(languageFile), workingFolder + "SHFBContent.xml");

                    // Copy the stop word list
                    languageFile = Path.Combine(ComponentUtilities.ToolsFolder, @"PresentationStyles\Shared\" +
                        @"StopWordList\" + Path.GetFileNameWithoutExtension(languageFile) +".txt");
                    File.Copy(languageFile, workingFolder + "StopWordList.txt");
                    File.SetAttributes(workingFolder + "StopWordList.txt", FileAttributes.Normal);

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // Generate the API filter used by MRefBuilder
                this.GenerateApiFilter();

                // Generate the reflection information
                this.ReportProgress(BuildStep.GenerateReflectionInfo, "Generating reflection information...");

                reflectionFile = workingFolder + "reflection.org";

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    this.TransformTemplate("MRefBuilder.config", templateFolder, workingFolder);
                    scriptFile = this.TransformTemplate("GenerateRefInfo.proj", templateFolder, workingFolder);

                    try
                    {
                        msBuildProject = new Project(scriptFile);

                        // Add the references
                        foreach(var r in referenceDictionary.Values)
                        {
                            projectItem = msBuildProject.AddItem(r.Item1, r.Item2, r.Item3)[0];

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

                                if(!Path.IsPathRooted(hintPath) && hintPath.IndexOf("$(",
                                  StringComparison.Ordinal) == -1)
                                {
                                    hintPath = FilePath.GetFullPath(Path.Combine(projectFolder, hintPath));

                                    // If the full path length would exceed the system maximums, make it relative
                                    // to keep it under the maximum lengths.
                                    if(hintPath.Length > 259 || Path.GetDirectoryName(hintPath).Length > 247)
                                        hintPath = FolderPath.AbsoluteToRelativePath(workingFolder, hintPath);

                                    projectItem.SetMetadataValue(ProjectElement.HintPath, hintPath);
                                }
                            }
                        }

                        // Add the assemblies to document
                        foreach(string assemblyName in assembliesList)
                            msBuildProject.AddItem("Assembly", assemblyName);

                        msBuildProject.Save(scriptFile);
                    }
                    finally
                    {
                        // If we loaded it, we must unload it.  If not, it is cached and may cause problems later.
                        if(msBuildProject != null)
                        {
                            ProjectCollection.GlobalProjectCollection.UnloadProject(msBuildProject);
                            ProjectCollection.GlobalProjectCollection.UnloadProject(msBuildProject.Xml);
                        }
                    }

                    this.ExecutePlugIns(ExecutionBehaviors.Before);

                    // Silverlight build targets are only available for 32-bit builds regardless of the framework
                    // version and require the 32-bit version of MSBuild in order to load the target file correctly.
                    if(project.FrameworkVersion.StartsWith("Silverlight", StringComparison.OrdinalIgnoreCase) &&
                      msBuildExePath.IndexOf("Framework64", StringComparison.OrdinalIgnoreCase) != -1)
                        this.RunProcess(msBuildExePath.Replace("Framework64", "Framework"),
                            "/nologo /clp:NoSummary /v:n GenerateRefInfo.proj");
                    else
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:n GenerateRefInfo.proj");

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // If this was a partial build used to obtain API information, stop now
                if(this.PartialBuildType == PartialBuildType.GenerateReflectionInfo)
                {
                    commentsFiles.Save();
                    goto AllDone;       // Yeah, I know it's evil but it's quick
                }

                // Transform the reflection output based on the document model and create the topic manifest
                this.ReportProgress(BuildStep.TransformReflectionInfo, "Transforming reflection output...");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    scriptFile = this.TransformTemplate("TransformManifest.proj", templateFolder, workingFolder);

                    this.ExecutePlugIns(ExecutionBehaviors.Before);
                    this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:n TransformManifest.proj");

                    // Change the reflection file extension before running the ExecutionBehaviors.After plug-ins
                    // so that the plug-ins (if any) get the correct filename.
                    reflectionFile = Path.ChangeExtension(reflectionFile, ".xml");

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }
                else
                    reflectionFile = Path.ChangeExtension(reflectionFile, ".xml");

                // If this was a partial build used to obtain information for namespace and namespace group
                // comments, stop now.
                if(this.PartialBuildType == PartialBuildType.TransformReflectionInfo)
                {
                    commentsFiles.Save();
                    goto AllDone;       // Yeah, I know it's evil but it's quick
                }

                // Load the transformed reflection information file
                reflectionFile = workingFolder + "reflection.xml";

                reflectionInfo = new XmlDocument();
                reflectionInfo.Load(reflectionFile);
                apisNode = reflectionInfo.SelectSingleNode("reflection/apis");

                // If there is nothing to document, stop the build
                if(apisNode.ChildNodes.Count == 0)
                    throw new BuilderException("BE0033", "No APIs found to document.  See error topic in " +
                        "help file for details.");

                // Generate namespace summary information
                this.GenerateNamespaceSummaries();

                // Expand <inheritdoc /> tags?
                if(commentsFiles.ContainsInheritedDocumentation)
                {
                    commentsFiles.Save();

                    // Transform the reflection output.
                    this.ReportProgress(BuildStep.GenerateInheritedDocumentation,
                        "Generating inherited documentation...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.TransformTemplate("GenerateInheritedDocs.config", templateFolder, workingFolder);
                        scriptFile = this.TransformTemplate("GenerateInheritedDocs.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m GenerateInheritedDocs.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // This should always be last so that it overrides comments in the project XML comments files
                    commentsFiles.Add(new XmlCommentsFile(workingFolder + "_InheritedDocs_.xml"));
                }

                commentsFiles.Save();

                this.GarbageCollect();

                this.EnsureOutputFoldersExist("html");

                // Copy conceptual content files if there are topics or tokens.  Tokens can be replaced in
                // XML comments files so we check for them too.
                if(conceptualContent == null)
                    conceptualContent = new ConceptualContentSettings(project);

                if(conceptualContent.ContentLayoutFiles.Count != 0 || conceptualContent.TokenFiles.Count != 0)
                {
                    this.ReportProgress(BuildStep.CopyConceptualContent, "Copying conceptual content...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        conceptualContent.CopyContentFiles(this);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.ReportProgress(BuildStep.CreateConceptualTopicConfigs,
                        "Creating conceptual topic configuration files...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        conceptualContent.CreateConfigurationFiles(this);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }
                else    // Create an empty xmlComp folder required by the build configuration
                    Directory.CreateDirectory(Path.Combine(workingFolder, "xmlComp"));

                // Copy the additional content
                this.CopyAdditionalContent();

                // Merge the conceptual and additional content TOC info
                this.MergeConceptualAndAdditionalContentTocInfo();

                // Generate the intermediate table of contents file.  This
                // must occur prior to running BuildAssembler as the MS Help
                // Viewer build component is dependent on the toc.xml file.
                this.ReportProgress(BuildStep.GenerateIntermediateTableOfContents,
                    "Generating intermediate table of contents file...");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    scriptFile = this.TransformTemplate("GenerateIntermediateTOC.proj", templateFolder, workingFolder);

                    this.ExecutePlugIns(ExecutionBehaviors.Before);

                    this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:n GenerateIntermediateTOC.proj");

                    // Determine the API content placement
                    this.DetermineApiContentPlacement();

                    // If there is conceptual content, generate the conceptual intermediate TOC
                    if(toc != null)
                    {
                        this.ReportProgress("Generating conceptual content intermediate TOC file...");

                        toc.SaveToIntermediateTocFile((project.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0 ?
                            this.RootContentContainerId : null, project.TocOrder, workingFolder + "_ConceptualTOC_.xml");
                    }

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // The June 2007 CTP removed the root namespace container from the TOC so we'll get
                // the default project page filename from the reflection information file.
                XmlNode defTopic = apisNode.SelectSingleNode("api[starts-with(@id, 'R:Project')]/file/@name");

                if(defTopic != null)
                {
                    namespacesTopic = defTopic.Value;

                    // Use it as the default topic if one wasn't specified explicitly in the additional content
                    if(defaultTopic == null)
                        defaultTopic = @"html\" + namespacesTopic + ".htm";
                }

                // Create the Sandcastle configuration file
                this.ReportProgress(BuildStep.CreateBuildAssemblerConfigs, "Creating Sandcastle configuration files...");

                // Add referenced namespaces to the hash set.  These are used to ensure just the needed set of
                // reflection target files are loaded by BuildAssembler and nothing more to save some time and
                // memory.
                var rn = this.ReferencedNamespaces;

                // These are all of the valid namespaces we are interested in.  This prevents the methods below
                // from returning nested types as potential namespaces since they can't tell the difference.
                HashSet<string> validNamespaces = new HashSet<string>(Directory.EnumerateFiles(
                    this.FrameworkReflectionDataFolder, "*.xml", SearchOption.AllDirectories).Select(
                        f => Path.GetFileNameWithoutExtension(f)));

                // Get namespaces referenced in the XML comments of the documentation sources
                foreach(var n in commentsFiles.GetReferencedNamespaces(validNamespaces))
                    rn.Add(n);

                // Get namespaces referenced in the reflection data (plug-ins are responsible for adding
                // additional namespaces if they add other reflection data files).
                foreach(string n in this.GetReferencedNamespaces(reflectionFile, validNamespaces))
                    rn.Add(n);

                // Get namespaces from the Framework comments files of the referenced namespaces.  This adds
                // references for stuff like designer and support classes not directly referenced anywhere else.
                foreach(string n in frameworkSettings.GetReferencedNamespaces(language, rn, validNamespaces).ToList())
                    rn.Add(n);

                // If F# syntax is being generated, add some of the F# namespaces as the syntax sections generate
                // references to types that may not be there in non-F# projects.
                if(ComponentUtilities.SyntaxFiltersFrom(syntaxGenerators, project.SyntaxFilters).Any(
                  f => f.Id == "F#"))
                {
                    rn.Add("Microsoft.FSharp.Core");
                    rn.Add("Microsoft.FSharp.Control");
                }

                // If there are no referenced namespaces, add System as a default to prevent the build components
                // from loading the entire set.
                if(rn.Count == 0)
                    rn.Add("System");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    this.ExecutePlugIns(ExecutionBehaviors.Before);

                    this.ReportProgress("    sandcastle.config");

                    // The configuration varies based on the style.  We'll use a common name (sandcastle.config).
                    resolvedPath = presentationStyle.ResolvePath(presentationStyle.ReferenceBuildConfiguration);
                    this.TransformTemplate(Path.GetFileName(resolvedPath), Path.GetDirectoryName(resolvedPath),
                        workingFolder);

                    if(!Path.GetFileName(resolvedPath).Equals("sandcastle.config", StringComparison.OrdinalIgnoreCase))
                        File.Move(workingFolder + Path.GetFileName(resolvedPath), workingFolder + "sandcastle.config");

                    // The conceptual content configuration file is only created if needed.
                    if(conceptualContent.ContentLayoutFiles.Count != 0)
                    {
                        this.ReportProgress("    conceptual.config");

                        resolvedPath = presentationStyle.ResolvePath(presentationStyle.ConceptualBuildConfiguration);

                        this.TransformTemplate(Path.GetFileName(resolvedPath), Path.GetDirectoryName(resolvedPath),
                            workingFolder);

                        if(!Path.GetFileName(resolvedPath).Equals("conceptual.config", StringComparison.OrdinalIgnoreCase))
                            File.Move(workingFolder + Path.GetFileName(resolvedPath), workingFolder + "conceptual.config");
                    }

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // Merge the build component custom configurations
                this.MergeComponentConfigurations();

                reflectionInfo = null;
                commentsFiles = null;
                apisNode = null;

                this.GarbageCollect();

                // Build the conceptual help topics
                if(conceptualContent.ContentLayoutFiles.Count != 0)
                {
                    this.ReportProgress(BuildStep.BuildConceptualTopics, "Building conceptual help topics...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("BuildConceptualTopics.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:n BuildConceptualTopics.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.GarbageCollect();
                }

                // Build the reference help topics
                this.ReportProgress(BuildStep.BuildReferenceTopics, "Building reference help topics...");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    scriptFile = this.TransformTemplate("BuildReferenceTopics.proj", templateFolder, workingFolder);

                    this.ExecutePlugIns(ExecutionBehaviors.Before);
                    this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:n BuildReferenceTopics.proj");
                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // Combine the conceptual and API intermediate TOC files into one
                this.CombineIntermediateTocFiles();

                // The last part differs based on the help file format
                if((project.HelpFileFormat & HelpFileFormats.Website) != 0)
                {
                    this.ReportProgress("\r\nClearing any prior web output");

                    // Purge all files and folders from the output path except for the working folder
                    // and the build log.  This is done first as the CHM and HxS files end up in the
                    // same output folder.  However, the website output is built last so that unnecessary
                    // files are not compiled into the CHM and HxS files.  Read-only and/or hidden files
                    // and folders are ignored as they are assumed to be under source control.
                    foreach(string file in Directory.EnumerateFiles(outputFolder))
                        if(!file.EndsWith(Path.GetFileName(this.LogFilename), StringComparison.Ordinal))
                            if((File.GetAttributes(file) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) == 0)
                                File.Delete(file);
                            else
                                this.ReportProgress("    Ignoring read-only/hidden file {0}", file);

                    foreach(string folder in Directory.EnumerateDirectories(outputFolder))
                        try
                        {
                            // Ignore the working folder
                            if(!folder.Equals(workingFolder.Substring(0, workingFolder.Length - 1), StringComparison.OrdinalIgnoreCase))
                            {
                                // Some source control providers have a mix of read-only/hidden files within a folder
                                // that isn't read-only/hidden (i.e. Subversion).  In such cases, leave the folder alone.
                                if(Directory.EnumerateFileSystemEntries(folder, "*", SearchOption.AllDirectories).Any(f =>
                                  (File.GetAttributes(f) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) != 0))
                                    this.ReportProgress("    Did not delete folder '{0}' as it contains " +
                                        "read-only or hidden folders/files", folder);
                                else
                                    if((File.GetAttributes(folder) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) == 0)
                                        Directory.Delete(folder, true);
                                    else
                                        this.ReportProgress("    Ignoring read-only/hidden folder {0}", folder);
                            }
                        }
                        catch(IOException ioEx)
                        {
                            this.ReportProgress("    Ignoring folder '{0}': {1}", folder, ioEx.Message);
                        }
                        catch(UnauthorizedAccessException uaEx)
                        {
                            this.ReportProgress("    Ignoring folder '{0}': {1}", folder, uaEx.Message);
                        }
                }

                if((project.HelpFileFormat & (HelpFileFormats.HtmlHelp1 | HelpFileFormats.Website)) != 0)
                {
                    this.ReportProgress(BuildStep.ExtractingHtmlInfo,
                        "Extracting HTML info for HTML Help 1 and/or website...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("ExtractHtmlInfo.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m ExtractHtmlInfo.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                // Copy the standard help file content.  This is done just before compiling the help so that
                // template files from the presentation style can take advantage of tag substitution.  By this
                // point, we should have everything we could possibly need.
                this.CopyStandardHelpContent();

                if((project.HelpFileFormat & HelpFileFormats.HtmlHelp1) != 0)
                {
                    // Generate the table of contents and set the default topic
                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Generating HTML Help 1 table of contents file...");

                    currentFormat = HelpFileFormats.HtmlHelp1;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        // It got created in the ExtractingHtmlInfo step above
                        // so there is actually nothing to do here.

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Generate the help file index
                    this.ReportProgress(BuildStep.GenerateHelpFileIndex, "Generating HTML Help 1 index file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        // It got created in the ExtractingHtmlInfo step above
                        // so there is actually nothing to do here.

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Generate the help project file
                    this.ReportProgress(BuildStep.GenerateHelpProject, "Generating HTML Help 1 project file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.TransformTemplate("Help1x.hhp", templateFolder, workingFolder);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Build the HTML Help 1 help file
                    this.ReportProgress(BuildStep.CompilingHelpFile, "Compiling HTML Help 1 file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("Build1xHelpFile.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Build1xHelpFile.proj");
                        this.GatherBuildOutputFilenames();
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                if((project.HelpFileFormat & HelpFileFormats.MSHelp2) != 0)
                {
                    // Generate the table of contents and set the default topic
                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Generating MS Help 2 table of contents file...");

                    currentFormat = HelpFileFormats.MSHelp2;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("Generate2xTOC.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:n Generate2xTOC.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Generate the help project files
                    this.ReportProgress(BuildStep.GenerateHelpProject, "Generating MS Help 2 project files...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        foreach(string projectFile in Directory.EnumerateFiles(templateFolder, "Help2x*.*"))
                            this.TransformTemplate(Path.GetFileName(projectFile), templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Build the MS Help 2 help file
                    this.ReportProgress(BuildStep.CompilingHelpFile, "Compiling MS Help 2 file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("Build2xHelpFile.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Build2xHelpFile.proj");

                        // Clean up the collection files
                        this.CleanUpCollectionFiles();

                        this.GatherBuildOutputFilenames();
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                if((project.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0)
                {
                    // The following build steps are executed to allow plug-ins to handle any necessary processing
                    // but nothing actually happens here:
                    //
                    //      BuildStep.GenerateHelpFormatTableOfContents
                    //      BuildStep.GenerateHelpProject
                    //
                    // For the MS Help Viewer format, there is no project file to compile and the TOC layout is
                    // generated when the help file is ultimately installed using metadata within each topic file.
                    // All of the necessary TOC info is stored in the intermediate TOC file generated prior to
                    // building the topics.  The BuildAssembler MSHCComponent inserts the TOC info into each topic
                    // as it is built.

                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Executing informational Generate Table of Contents " +
                        "build step for plug-ins (not used for MS Help Viewer)");

                    currentFormat = HelpFileFormats.MSHelpViewer;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.ReportProgress(BuildStep.GenerateHelpProject,
                        "Executing informational Generate Help Project " +
                        "build step for plug-ins (not used for MS Help Viewer)");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Build the MS Help Viewer help file
                    this.ReportProgress(BuildStep.CompilingHelpFile, "Generating MS Help Viewer file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.TransformTemplate("HelpContentSetup.msha", templateFolder, workingFolder);

                        // Rename the content setup file to use the help filename to keep them related and
                        // so that multiple output files can be sent to the same output folder.
                        File.Move(workingFolder + "HelpContentSetup.msha", workingFolder + this.ResolvedHtmlHelpName + ".msha");

                        // Generate the example install and remove scripts
                        this.TransformTemplate("InstallMSHC.bat", templateFolder, workingFolder);
                        File.Move(workingFolder + "InstallMSHC.bat", workingFolder + "Install_" + this.ResolvedHtmlHelpName + ".bat");

                        this.TransformTemplate("RemoveMSHC.bat", templateFolder, workingFolder);
                        File.Move(workingFolder + "RemoveMSHC.bat", workingFolder + "Remove_" + this.ResolvedHtmlHelpName + ".bat");

                        // Copy the launcher utility
                        File.Copy(ComponentUtilities.ToolsFolder + "HelpLibraryManagerLauncher.exe",
                            workingFolder + "HelpLibraryManagerLauncher.exe");
                        File.SetAttributes(workingFolder + "HelpLibraryManagerLauncher.exe", FileAttributes.Normal);

                        scriptFile = this.TransformTemplate("BuildHelpViewerFile.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildHelpViewerFile.proj");

                        this.GatherBuildOutputFilenames();
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                if((project.HelpFileFormat & HelpFileFormats.Website) != 0)
                {
                    // Generate the table of contents and set the default topic
                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Generating website table of contents file...");

                    currentFormat = HelpFileFormats.Website;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        // It got created in the ExtractingHtmlInfo step above
                        // so there is actually nothing to do here.

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.GenerateWebsite();
                }

                if((project.HelpFileFormat & HelpFileFormats.OpenXml) != 0)
                {
                    // The following build steps are executed to allow plug-ins to handle any necessary processing
                    // but nothing actually happens here:
                    //
                    //      BuildStep.GenerateHelpFormatTableOfContents
                    //      BuildStep.GenerateHelpProject
                    //
                    // For the Open XML format, there is no project file to compile and the TOC layout is
                    // generated when the document is opened.  All of the necessary TOC info is stored in the
                    // intermediate TOC file generated prior to building the topics.  The process used to merge
                    // the topics into a single document uses it to define the order in which the topics are
                    // combined.

                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Executing informational " +
                        "Generate Table of Contents build step for plug-ins (not used for Open XML)");

                    currentFormat = HelpFileFormats.OpenXml;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.ReportProgress(BuildStep.GenerateHelpProject, "Executing informational Generate Help " +
                        "Project build step for plug-ins (not used for Open XML)");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Build the Open XML document
                    this.ReportProgress(BuildStep.CompilingHelpFile, "Generating Open XML document file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("BuildOpenXmlFile.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildOpenXmlFile.proj");

                        this.GatherBuildOutputFilenames();
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                // All done
                if(project.CleanIntermediates)
                {
                    this.ReportProgress(BuildStep.CleanIntermediates, "Removing intermediate files...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        try
                        {
                            Directory.Delete(workingFolder, true);
                        }
                        catch(IOException ioEx)
                        {
                            this.ReportProgress("    Not all build output was removed from '{0}': {1}",
                                workingFolder, ioEx.Message);
                        }
                        catch(UnauthorizedAccessException uaEx)
                        {
                            this.ReportProgress("    Not all build output was removed from '{0}': {1}",
                                workingFolder, uaEx.Message);
                        }

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }
AllDone:
                progressArgs.HasCompleted = true;

                TimeSpan runtime = DateTime.Now - buildStart;

                this.ReportProgress(BuildStep.Completed, "\r\nBuild completed successfully at {0}.  " +
                    "Total time: {1:00}:{2:00}:{3:00.0000}\r\n", DateTime.Now, Math.Floor(runtime.TotalSeconds / 3600),
                    Math.Floor((runtime.TotalSeconds % 3600) / 60), (runtime.TotalSeconds % 60));

                System.Diagnostics.Debug.WriteLine("Build process finished successfully\r\n");
            }
            catch(ThreadAbortException )
            {
                // Kill off the process, known child processes, and the STDOUT and STDERR threads too if
                // necessary.
                if(currentProcess != null && !currentProcess.HasExited)
                {
                    DateTime procStart;

                    // Only kill potential matches started after the current process's start time.  It's not
                    // perfect if you've got two or more SHFB builds running concurrently but it's the best
                    // we can do without getting really complicated which I'm not prepared to do since this is
                    // an extremely low occurrence issue.
                    try
                    {
                        procStart = currentProcess.StartTime;
                        currentProcess.Kill();
                    }
                    catch
                    {
                        // If we can't get the start time, assume the build start time
                        procStart = buildStart;
                    }

                    foreach(Process p in Process.GetProcesses())
                        try
                        {
                            if(reKillProcess.IsMatch(p.ProcessName) && !p.HasExited && p.StartTime > procStart)
                            {
                                System.Diagnostics.Debug.WriteLine("Killing " + p.ProcessName);
                            
                                p.Kill();
                            }
                        }
                        catch
                        {
                            // Ignore exceptions, the process had probably already exited
                        }
                }

                if(stdOutThread != null && stdOutThread.IsAlive)
                {
                    stdOutThread.Abort();
                    waitCount = 0;

                    while(waitCount < 5 && !stdOutThread.Join(1000))
                        waitCount++;
                }

                if(stdErrThread != null && stdErrThread.IsAlive)
                {
                    stdErrThread.Abort();
                    waitCount = 0;

                    while(waitCount < 5 && !stdErrThread.Join(1000))
                        waitCount++;
                }

                progressArgs.HasCompleted = true;
                this.ReportError(BuildStep.Canceled, "BE0064", "BUILD CANCELLED BY USER");
                System.Diagnostics.Debug.WriteLine("Build process aborted\r\n");
            }
            catch(Exception ex)
            {
                BuilderException bex = ex as BuilderException;
                System.Diagnostics.Debug.WriteLine(ex);
                progressArgs.HasCompleted = true;

                do
                {
                    if(message != null)
                        message += "\r\n";

                    message += ex.Message + "\r\n" + ex.StackTrace;
                    ex = ex.InnerException;

                } while(ex != null);

                // NOTE: Message may contain format markers so pass it as a format argument
                if(bex != null)
                    this.ReportError(BuildStep.Failed, bex.ErrorCode, "{0}", message);
                else
                    this.ReportError(BuildStep.Failed, "BE0065", "BUILD FAILED: {0}", message);

                System.Diagnostics.Debug.WriteLine("Build process failed\r\n");
            }
            finally
            {
                if(currentProcess != null)
                    currentProcess.Dispose();

                try
                {
                    this.ExecutePlugIns(ExecutionBehaviors.Before);
                }
                catch(Exception ex)
                {
                    // Not much we can do at this point...
                    this.ReportProgress(ex.ToString());
                }

                try
                {
                    this.ExecutePlugIns(ExecutionBehaviors.After);

                    if(componentContainer != null)
                        componentContainer.Dispose();
                }
                catch(Exception ex)
                {
                    // Not much we can do at this point...
                    this.ReportProgress(ex.ToString());
                }
                finally
                {
                    this.GarbageCollect();

                    if(swLog != null)
                    {
                        swLog.WriteLine("</buildStep>\r\n</shfbBuild>");
                        swLog.Close();
                        swLog = null;
                    }

                    // If we created a copy of the project, dispose of it and return to the original
                    if(originalProject != null)
                    {
                        project.Dispose();
                        project = originalProject;
                    }

                    if(progressArgs.BuildStep == BuildStep.Completed && !project.KeepLogFile)
                        File.Delete(this.LogFilename);
                }
            }
        }
예제 #2
0
        //=====================================================================
        /// <summary>
        /// Call this method to perform the build on the project.
        /// </summary>
        /// <event cref="BuildStepChanged">This event fires when the
        /// current build step changes.</event>
        /// <event cref="BuildProgress">This event fires to report progress
        /// information.</event>
        public void Build()
        {
            Project msBuildProject;
            BuildItem buildItem;
            Version v;
            string helpFile, languageFile, scriptFile, message = null;
            int waitCount;

            System.Diagnostics.Debug.WriteLine("Build process starting\r\n");

            try
            {
                Assembly asm = Assembly.GetExecutingAssembly();

                FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(asm.Location);
                this.ReportProgress(BuildStep.Initializing,
                    "[{0}, version {1}]", fvi.ProductName, fvi.ProductVersion);

                buildStart = stepStart = DateTime.Now;

                msBuildExePath = Path.Combine(Engine.GlobalEngine.Toolsets[
                    project.MSBuildProject.ToolsVersion].ToolsPath, "MSBuild.exe");

                // Base folder for SHFB
                shfbFolder = Path.GetDirectoryName(asm.Location) + @"\";

                // Get the location of the template files
                templateFolder = shfbFolder + @"Templates\";

                // Get the location of the web files
                webFolder = shfbFolder + @"\Web\";

                // Make sure we start out in the project's output folder
                // in case the output folder is relative to it.
                projectFolder = Path.GetDirectoryName(originalProjectName);
                if(projectFolder.Length == 0)
                    projectFolder = Directory.GetCurrentDirectory();

                projectFolder += @"\";

                Directory.SetCurrentDirectory(projectFolder);

                this.ReportProgress("Creating output and working folders...");

                outputFolder = project.OutputPath;
                if(String.IsNullOrEmpty(outputFolder))
                    outputFolder = Directory.GetCurrentDirectory();
                else
                    outputFolder = Path.GetFullPath(outputFolder);

                if(!Directory.Exists(outputFolder))
                    Directory.CreateDirectory(outputFolder);

                if(outputFolder[outputFolder.Length - 1] != '\\')
                    outputFolder += @"\";

                // Create the log file.  The log may be in a folder other than
                // the output so make sure it exists too.
                if(!Directory.Exists(Path.GetDirectoryName(this.LogFilename)))
                    Directory.CreateDirectory(Path.GetDirectoryName(this.LogFilename));

                swLog = new StreamWriter(this.LogFilename);
                swLog.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                    "\r\n<shfbBuild product=\"{0}\" version=\"{1}\" " +
                    "projectFile=\"{2}\" started=\"{3}\">\r\n" +
                    "<buildStep step=\"{4}\">",
                    fvi.ProductName, fvi.ProductVersion, originalProjectName,
                    DateTime.Now, BuildStep.Initializing);

                if(project.WorkingPath.Path.Length == 0)
                    workingFolder = outputFolder + @"Working\";
                else
                    workingFolder = project.WorkingPath;

                if((project.HelpFileFormat & HelpFileFormat.Website) != 0)
                    BuildProcess.VerifySafePath("OutputPath", outputFolder, projectFolder);

                // The output folder and the working folder cannot be the same
                if(workingFolder == outputFolder)
                    throw new BuilderException("BE0030", "The OutputPath and " +
                        "WorkingPath properties cannot be set to the same path");

                // For MS Help 2, the HTML Help Name cannot contain spaces
                if((project.HelpFileFormat & HelpFileFormat.MSHelp2) != 0 &&
                  project.HtmlHelpName.IndexOf(' ') != -1)
                    throw new BuilderException("BE0031", "For MS Help 2 " +
                        "builds, the HtmlHelpName property cannot contain " +
                        "spaces as they are not valid in the collection name.");

                // Check for the SHFBROOT environment variable.  It may not be
                // present yet if a reboot hasn't occurred after installation.
                // In such cases, set it to the proper folder for this process
                // so that projects can be loaded and built.
                if(Environment.GetEnvironmentVariable("SHFBROOT") == null)
                {
                    // We won't issue a warning since it may not be defined
                    // in some build environments such as on a build server.
                    // In such cases, it is passed in as a command line
                    // option to MSBuild.  Storing it in the environment here
                    // lets the SHFB build projects work as expected.
                    this.ReportProgress("The SHFBROOT system environment variable was " +
                        "not found.  This variable is usually created during installation " +
                        "and may require a reboot.  It has been defined temporarily " +
                        "for this process as: SHFBROOT={0}", shfbFolder);

                    Environment.SetEnvironmentVariable("SHFBROOT", shfbFolder);
                }

                if(project.PlugInConfigurations.Count != 0)
                    this.LoadPlugIns();

                this.ExecutePlugIns(ExecutionBehaviors.After);

                try
                {
                    if(Directory.Exists(workingFolder))
                    {
                        // Clear any data from a prior run
                        this.ReportProgress(BuildStep.ClearWorkFolder, "Clearing working folder...");
                        BuildProcess.VerifySafePath("WorkingPath", workingFolder, projectFolder);

                        if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                        {
                            this.ExecutePlugIns(ExecutionBehaviors.Before);

                            try
                            {
                                Directory.Delete(workingFolder, true);
                            }
                            catch(IOException ioEx)
                            {
                                this.ReportProgress("    Not all prior output was removed from '{0}': {1}",
                                    workingFolder, ioEx.Message);
                            }
                            catch(UnauthorizedAccessException uaEx)
                            {
                                this.ReportProgress("    Not all prior output was removed from '{0}': {1}",
                                    workingFolder, uaEx.Message);
                            }

                            this.ExecutePlugIns(ExecutionBehaviors.After);
                        }
                    }

                    // If the help file is open, it will fail to build so try
                    // to get rid of it now before we get too far into it.
                    helpFile = outputFolder + project.HtmlHelpName + ".chm";

                    if((project.HelpFileFormat & HelpFileFormat.HtmlHelp1) != 0 && File.Exists(helpFile))
                        File.Delete(helpFile);

                    helpFile = Path.ChangeExtension(helpFile, ".hxs");

                    if((project.HelpFileFormat & HelpFileFormat.MSHelp2) != 0 && File.Exists(helpFile))
                        File.Delete(helpFile);

                    helpFile = Path.ChangeExtension(helpFile, ".mshc");

                    if((project.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0 && File.Exists(helpFile))
                        File.Delete(helpFile);

                    if((project.HelpFileFormat & HelpFileFormat.Website) != 0)
                    {
                        helpFile = outputFolder + "Index.aspx";

                        if(File.Exists(helpFile))
                            File.Delete(helpFile);

                        helpFile = Path.ChangeExtension(helpFile, ".html");

                        if(File.Exists(helpFile))
                            File.Delete(helpFile);
                    }
                }
                catch(IOException ex)
                {
                    throw new BuilderException("BE0025", "Unable to remove prior build output: " + ex.Message);
                }
                catch
                {
                    throw;
                }

                Directory.CreateDirectory(workingFolder);

                // Make sure we can find the tools
                this.FindTools();

                if(!Directory.Exists(sandcastleFolder + @"Data\Reflection"))
                    throw new BuilderException("BE0032", "Reflection data files do not exist yet");

                // Make sure the HelpFileVersion property is in the form of
                // a real version number.
                try
                {
                    if(project.HelpFileVersion.IndexOf('{') == -1)
                        v = new Version(project.HelpFileVersion);
                    else
                        v = new Version(this.TransformText(project.HelpFileVersion));

                    if(v.Build == -1 || v.Revision == -1)
                        throw new FormatException("The version number must specify all four parts.  " +
                            "Specify zero for unused parts.");
                }
                catch(Exception ex)
                {
                    throw new BuilderException("BE0066", "The HelpFileVersion property value '" +
                        project.HelpFileVersion + "' is not in the correct format (#.#.#.#)", ex);
                }

                this.GarbageCollect();

                // Validate the documentation source information, gather
                // assembly and reference info, and copy XML comments files
                // to the working folder.
                this.ValidateDocumentationSources();

                // Transform the shared builder content files
                language = project.Language;
                languageFile = "SharedBuilderContent_" + language.Name + ".xml";

                this.ReportProgress(BuildStep.GenerateSharedContent, "Generating shared content files ({0}, {1})...",
                    language.Name, language.DisplayName);

                // First we need to figure out which style is in effect.
                // Base it on whether the presentation style folder contains
                // "v2005", "hana", or "prototype".
                presentationParam = project.PresentationStyle.ToLower(CultureInfo.InvariantCulture);

                if(presentationParam.IndexOf("vs2005", StringComparison.Ordinal) != -1)
                    presentationParam = "vs2005";
                else
                    if(presentationParam.IndexOf("hana", StringComparison.Ordinal) != -1)
                        presentationParam = "hana";
                    else
                    {
                        if(presentationParam.IndexOf("prototype", StringComparison.Ordinal) == -1)
                            this.ReportWarning("BE0001", "Unable to determine presentation style from folder " +
                                "'{0}'.  Assuming Prototype style.", project.PresentationStyle);

                        presentationParam = "prototype";
                    }

                if(!File.Exists(templateFolder + @"..\SharedContent\" + languageFile))
                {
                    languageFile = "SharedBuilderContent_en-US.xml";

                    // Warn the user about the default being used
                    this.ReportWarning("BE0002", "Shared builder content " +
                        "for the '{0}, {1}' language could not be found.  " +
                        "Using 'en-US, English (US)' defaults.",
                        language.Name, language.DisplayName);
                }

                // See if the user has translated the Sandcastle resources.
                // If not found, default to US English.
                languageFolder = presentationFolder + @"\Content\" + language.Name;

                if(Directory.Exists(languageFolder))
                    languageFolder = language.Name + @"\";
                else
                {
                    // Warn the user about the default being used.  The
                    // language will still be used for the help file though.
                    if(language.Name != "en-US")
                        this.ReportWarning("BE0003", "Sandcastle shared " +
                            "content for the '{0}, {1}' language could not " +
                            "be found.  Using 'en-US, English (US)' " +
                            "defaults.", language.Name, language.DisplayName);

                    languageFolder = String.Empty;
                }

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    this.ExecutePlugIns(ExecutionBehaviors.Before);

                    this.TransformTemplate(languageFile, templateFolder + @"..\SharedContent\", workingFolder);
                    File.Move(workingFolder + languageFile, workingFolder + "SharedBuilderContent.xml");

                    // Presentation-style specific shared content
                    languageFile = languageFile.Replace("Shared", presentationParam);

                    this.TransformTemplate(languageFile, templateFolder + @"..\SharedContent\", workingFolder);
                    File.Move(workingFolder + languageFile, workingFolder + "PresentationStyleBuilderContent.xml");

                    // Copy the stop word list
                    languageFile = Path.ChangeExtension(languageFile.Replace(presentationParam +
                        "BuilderContent", "StopWordList"), ".txt");
                    File.Copy(templateFolder + @"..\SharedContent\" + languageFile, workingFolder + "StopWordList.txt");
                    File.SetAttributes(workingFolder + "StopWordList.txt", FileAttributes.Normal);

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // Generate the API filter used by MRefBuilder
                this.GenerateApiFilter();

                // Generate the reflection information
                this.ReportProgress(BuildStep.GenerateReflectionInfo, "Generating reflection information...");

                reflectionFile = workingFolder + "reflection.org";

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    this.TransformTemplate("MRefBuilder.config", templateFolder, workingFolder);
                    scriptFile = this.TransformTemplate("GenerateRefInfo.proj", templateFolder, workingFolder);

                    msBuildProject = new Project(Engine.GlobalEngine);
                    msBuildProject.Load(scriptFile);

                    // Add the references
                    foreach(BuildItem item in referenceDictionary.Values)
                    {
                        buildItem = msBuildProject.AddNewItem(item.Name, item.Include);
                        item.CopyCustomMetadataTo(buildItem);
                    }

                    // Add the assemblies to document
                    foreach(string assemblyName in assembliesList)
                        buildItem = msBuildProject.AddNewItem("Assembly", assemblyName);

                    msBuildProject.Save(scriptFile);

                    this.ExecutePlugIns(ExecutionBehaviors.Before);
                    this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m GenerateRefInfo.proj");
                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                reflectionInfo = new XmlDocument();
                reflectionInfo.Load(reflectionFile);
                apisNode = reflectionInfo.SelectSingleNode("reflection/apis");

                // Generate namespace summary information
                this.GenerateNamespaceSummaries();

                // Remove unwanted items from the reflection information file
                this.ApplyVisibilityProperties();

                // If there is nothing to document, stop the build
                if(apisNode.ChildNodes.Count == 0)
                    throw new BuilderException("BE0033", "No APIs found to " +
                        "document.  See error topic in help file for details.");

                reflectionInfo = null;
                apisNode = null;

                // If this was a partial build used to obtain API information,
                // stop now.
                if(isPartialBuild)
                {
                    commentsFiles.Save();
                    goto AllDone;       // Yeah, I know it's evil but it's quick
                }

                // Expand <inheritdoc /> tags?
                if(commentsFiles.ContainsInheritedDocumentation)
                {
                    commentsFiles.Save();

                    // Transform the reflection output.
                    this.ReportProgress(BuildStep.GenerateInheritedDocumentation,
                        "Generating inherited documentation...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.TransformTemplate("GenerateInheritedDocs.config", templateFolder, workingFolder);
                        scriptFile = this.TransformTemplate("GenerateInheritedDocs.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m GenerateInheritedDocs.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // This should always be last so that it overrides comments in the project XML comments files
                    commentsFiles.Add(new XmlCommentsFile(workingFolder + "_InheritedDocs_.xml"));
                }

                this.GarbageCollect();

                // Transform the reflection output.
                this.ReportProgress(BuildStep.TransformReflectionInfo, "Transforming reflection output...");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    scriptFile = this.TransformTemplate("TransformManifest.proj", templateFolder, workingFolder);

                    this.ExecutePlugIns(ExecutionBehaviors.Before);
                    this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m TransformManifest.proj");
                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // Load the transformed file
                reflectionFile = reflectionFile.Replace(".org", ".xml");
                reflectionInfo = new XmlDocument();
                reflectionInfo.Load(reflectionFile);
                apisNode = reflectionInfo.SelectSingleNode("reflection/apis");

                // Alter the help topic filenames if necessary
                this.ModifyHelpTopicFilenames();

                // Backup the original reflection file for reference and save the changed file
                File.Copy(reflectionFile, Path.ChangeExtension(reflectionFile, ".bak"), true);

                reflectionInfo.Save(reflectionFile);
                commentsFiles.Save();

                // Copy the standard help file content
                this.CopyStandardHelpContent();

                // Copy conceptual content files if there are topics
                conceptualContent = new ConceptualContentSettings(project);

                if(conceptualContent.ContentLayoutFiles.Count != 0)
                {
                    this.ReportProgress(BuildStep.CopyConceptualContent, "Copying conceptual content...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        conceptualContent.CopyContentFiles(this);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.ReportProgress(BuildStep.CreateConceptualTopicConfigs,
                        "Creating conceptual topic configuration files...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        conceptualContent.CreateConfigurationFiles(this);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                // Copy the additional content
                this.CopyAdditionalContent();

                // Merge the conceptual and additional content TOC info
                this.MergeConceptualAndAdditionalContentTocInfo();

                // Generate the intermediate table of contents file.  This
                // must occur prior to running BuildAssembler as the MS Help
                // Viewer build component is dependent on the toc.xml file.
                this.ReportProgress(BuildStep.GenerateIntermediateTableOfContents,
                    "Generating intermediate table of contents file...");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    scriptFile = this.TransformTemplate("GenerateIntermediateTOC.proj", templateFolder, workingFolder);

                    this.ExecutePlugIns(ExecutionBehaviors.Before);

                    this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m GenerateIntermediateTOC.proj");

                    // Determine the API content placement
                    this.DetermineApiContentPlacement();

                    // If there is conceptual content, generate the conceptual intermediate TOC
                    if(toc != null)
                    {
                        this.ReportProgress("Generating conceptual content intermediate TOC file...");

                        toc.SaveToIntermediateTocFile((project.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0 ?
                            this.RootContentContainerId : null, project.TocOrder, workingFolder + "_ConceptualTOC_.xml");
                    }

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // The June 2007 CTP removed the root namespace container from the TOC so we'll get
                // the default project page filename from the refelection information file.
                XmlNode defTopic = apisNode.SelectSingleNode("api[@id='R:Project']/file/@name");

                if(defTopic != null)
                {
                    namespacesTopic = defTopic.Value;

                    // Use it as the default topic if one wasn't specified explicitly in the additional content
                    if(defaultTopic == null)
                        defaultTopic = @"html\" + namespacesTopic + ".htm";
                }

                // Create the Sandcastle configuration file
                this.ReportProgress(BuildStep.CreateBuildAssemblerConfigs, "Creating Sandcastle configuration files...");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    this.ExecutePlugIns(ExecutionBehaviors.Before);

                    this.ReportProgress("    sandcastle.config");

                    // The configuration varies based on the style.  However,
                    // we'll use a common name (sandcastle.config).
                    this.TransformTemplate(presentationParam + ".config", templateFolder, workingFolder);
                    File.Move(workingFolder + presentationParam + ".config", workingFolder + "sandcastle.config");

                    // The conceptual content configuration file is common to
                    // all styles.  It is only created if needed.
                    if(conceptualContent.ContentLayoutFiles.Count != 0)
                    {
                        this.ReportProgress("    conceptual.config");
                        this.TransformTemplate("conceptual.config", templateFolder, workingFolder);
                    }

                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // Merge the build component custom configurations
                this.MergeComponentConfigurations();

                reflectionInfo = null;
                commentsFiles = null;
                apisNode = null;

                this.GarbageCollect();

                // Build the conceptual help topics
                if(conceptualContent.ContentLayoutFiles.Count != 0)
                {
                    this.ReportProgress(BuildStep.BuildConceptualTopics, "Building conceptual help topics...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("BuildConceptualTopics.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildConceptualTopics.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.GarbageCollect();
                }

                // Build the reference help topics
                this.ReportProgress(BuildStep.BuildReferenceTopics, "Building reference help topics...");

                if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                {
                    scriptFile = this.TransformTemplate("BuildReferenceTopics.proj", templateFolder, workingFolder);

                    this.ExecutePlugIns(ExecutionBehaviors.Before);
                    this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildReferenceTopics.proj");
                    this.ExecutePlugIns(ExecutionBehaviors.After);
                }

                // Combine the conceptual and API intermediate TOC files into one
                this.CombineIntermediateTocFiles();

                // The last part differs based on the help file format
                if((project.HelpFileFormat & HelpFileFormat.Website) != 0)
                {
                    this.ReportProgress("\r\nClearing any prior web output");

                    // Purge all files and folders from the output path except for the working folder
                    // and the build log.  This is done first as the CHM and HxS files end up in the
                    // same output folder.  However, the website output is built last so that unnecessary
                    // files are not compiled into the CHM and HxS files.  Read-only and/or hidden files
                    // and folders are ignored as they are assumed to be under source control.
                    foreach(string file in Directory.GetFiles(outputFolder))
                        if(!file.EndsWith(Path.GetFileName(this.LogFilename), StringComparison.Ordinal))
                            if((File.GetAttributes(file) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) == 0)
                                File.Delete(file);
                            else
                                this.ReportProgress("    Ignoring read-only/hidden file {0}", file);

                    foreach(string folder in Directory.GetDirectories(outputFolder))
                        try
                        {
                            // Ignore the working folder
                            if(!folder.EndsWith("Working", StringComparison.Ordinal))
                              if((File.GetAttributes(folder) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) == 0)
                                    Directory.Delete(folder, true);
                                else
                                    this.ReportProgress("    Ignoring read-only/hidden folder {0}", folder);
                        }
                        catch(IOException ioEx)
                        {
                            this.ReportProgress("    Ignoring folder '{0}': {1}", folder, ioEx.Message);
                        }
                        catch(UnauthorizedAccessException uaEx)
                        {
                            this.ReportProgress("    Ignoring folder '{0}': {1}", folder, uaEx.Message);
                        }
                }

                if((project.HelpFileFormat & (HelpFileFormat.HtmlHelp1 | HelpFileFormat.Website)) != 0)
                {
                    this.ReportProgress(BuildStep.ExtractingHtmlInfo,
                        "Extracting HTML info for HTML Help 1 and/or website...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("ExtractHtmlInfo.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m ExtractHtmlInfo.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                if((project.HelpFileFormat & HelpFileFormat.HtmlHelp1) != 0)
                {
                    // Generate the table of contents and set the default topic
                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Generating HTML Help 1 table of contents file...");

                    currentFormat = HelpFileFormat.HtmlHelp1;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        // It got created in the ExtractingHtmlInfo step above
                        // so there is actually nothing to do here.

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Generate the help file index
                    this.ReportProgress(BuildStep.GenerateHelpFileIndex, "Generating HTML Help 1 index file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        // It got created in the ExtractingHtmlInfo step above
                        // so there is actually nothing to do here.

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Generate the help project file
                    this.ReportProgress(BuildStep.GenerateHelpProject, "Generating HTML Help 1 project file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.TransformTemplate("Help1x.hhp", templateFolder, workingFolder);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Build the HTML Help 1 help file
                    this.ReportProgress(BuildStep.CompilingHelpFile, "Compiling HTML Help 1 file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("Build1xHelpFile.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Build1xHelpFile.proj");
                        this.GatherBuildOutputFilenames();
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                if((project.HelpFileFormat & HelpFileFormat.MSHelp2) != 0)
                {
                    // Generate the table of contents and set the default topic
                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Generating MS Help 2 table of contents file...");

                    currentFormat = HelpFileFormat.MSHelp2;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("Generate2xTOC.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Generate2xTOC.proj");
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Generate the help project files
                    this.ReportProgress(BuildStep.GenerateHelpProject, "Generating MS Help 2 project files...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        string[] help2xFiles = Directory.GetFiles(templateFolder, "Help2x*.*");

                        foreach(string projectFile in help2xFiles)
                            this.TransformTemplate(Path.GetFileName(projectFile), templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Build the MS Help 2 help file
                    this.ReportProgress(BuildStep.CompilingHelpFile, "Compiling MS Help 2 file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        scriptFile = this.TransformTemplate("Build2xHelpFile.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Build2xHelpFile.proj");

                        // Clean up the collection files
                        this.CleanUpCollectionFiles();

                        this.GatherBuildOutputFilenames();
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                if((project.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0)
                {
                    // The following build steps are executed to allow plug-ins to handle any necessary processing
                    // but nothing actually happens here:
                    //
                    //      BuildStep.GenerateHelpFormatTableOfContents
                    //      BuildStep.GenerateHelpProject
                    //
                    // For the MS Help Viewer format, there is no project file to compile and the TOC layout is
                    // generated when the help file is ultimately installed using metadata within each topic file.
                    // All of the necessary TOC info is stored in the intermediate TOC file generated prior to
                    // building the topics.  The BuildAssembler MSHCComponent inserts the TOC info into each topic
                    // as it is built.

                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Executing informational Generate Table of Contents " +
                        "build step for plug-ins (not used for MS Help Viewer)");

                    currentFormat = HelpFileFormat.MSHelpViewer;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.ReportProgress(BuildStep.GenerateHelpProject,
                        "Executing informational Generate Help Project " +
                        "build step for plug-ins (not used for MS Help Viewer)");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    // Build the MS Help Viewer help file
                    this.ReportProgress(BuildStep.CompilingHelpFile, "Generating MS Help Viewer file...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.TransformTemplate("HelpContentSetup.msha", templateFolder, workingFolder);

                        // Rename the content setup file to use the help filename to keep them related and
                        // so that multiple output files can be sent to the same output folder.
                        File.Move(workingFolder + "HelpContentSetup.msha", workingFolder + project.HtmlHelpName + ".msha");

                        // Generate the example install and remove scripts
                        this.TransformTemplate("InstallMSHC.bat", templateFolder, workingFolder);
                        File.Move(workingFolder + "InstallMSHC.bat", workingFolder + "Install_" + project.HtmlHelpName + ".bat");

                        this.TransformTemplate("RemoveMSHC.bat", templateFolder, workingFolder);
                        File.Move(workingFolder + "RemoveMSHC.bat", workingFolder + "Remove_" + project.HtmlHelpName + ".bat");

                        // Copy the launcher utility
                        File.Copy(shfbFolder + "HelpLibraryManagerLauncher.exe", workingFolder + "HelpLibraryManagerLauncher.exe");
                        File.SetAttributes(workingFolder + "HelpLibraryManagerLauncher.exe", FileAttributes.Normal);

                        scriptFile = this.TransformTemplate("BuildHelpViewerFile.proj", templateFolder, workingFolder);

                        this.ExecutePlugIns(ExecutionBehaviors.Before);
                        this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildHelpViewerFile.proj");

                        this.GatherBuildOutputFilenames();
                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }

                if((project.HelpFileFormat & HelpFileFormat.Website) != 0)
                {
                    // Generate the table of contents and set the default topic
                    this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents,
                        "Generating website table of contents file...");

                    currentFormat = HelpFileFormat.Website;

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        // It got created in the ExtractingHtmlInfo step above
                        // so there is actually nothing to do here.

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }

                    this.GenerateWebsite();
                }

                // All done
                if(project.CleanIntermediates)
                {
                    this.ReportProgress(BuildStep.CleanIntermediates, "Removing intermediate files...");

                    if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
                    {
                        this.ExecutePlugIns(ExecutionBehaviors.Before);

                        try
                        {
                            Directory.Delete(workingFolder, true);
                        }
                        catch(IOException ioEx)
                        {
                            this.ReportProgress("    Not all build output was removed from '{0}': {1}",
                                workingFolder, ioEx.Message);
                        }
                        catch(UnauthorizedAccessException uaEx)
                        {
                            this.ReportProgress("    Not all build output was removed from '{0}': {1}",
                                workingFolder, uaEx.Message);
                        }

                        this.ExecutePlugIns(ExecutionBehaviors.After);
                    }
                }
            AllDone:
                progressArgs.HasCompleted = true;

                TimeSpan runtime = DateTime.Now - buildStart;

                this.ReportProgress(BuildStep.Completed,
                    "\r\nBuild completed successfully at {0:MM/dd/yyyy hh:mm tt}.  " +
                    "Total time: {1:00}:{2:00}:{3:00.0000}\r\n", DateTime.Now, Math.Floor(runtime.TotalSeconds / 3600),
                    Math.Floor((runtime.TotalSeconds % 3600) / 60), (runtime.TotalSeconds % 60));

                System.Diagnostics.Debug.WriteLine("Build process finished successfully\r\n");
            }
            catch(ThreadAbortException )
            {
                // Kill off the process, known child processes and the STDOUT
                // and STDERR threads too if necessary.
                if(currentProcess != null && !currentProcess.HasExited)
                {
                    currentProcess.Kill();

                    foreach(Process p in Process.GetProcesses())
                        if(reKillProcess.IsMatch(p.ProcessName))
                        {
                            System.Diagnostics.Debug.WriteLine("Killing " + p.ProcessName);
                            p.Kill();
                        }
                }

                if(stdOutThread != null && stdOutThread.IsAlive)
                {
                    stdOutThread.Abort();
                    waitCount = 0;

                    while(waitCount < 5 && !stdOutThread.Join(1000))
                        waitCount++;
                }

                if(stdErrThread != null && stdErrThread.IsAlive)
                {
                    stdErrThread.Abort();
                    waitCount = 0;

                    while(waitCount < 5 && !stdErrThread.Join(1000))
                        waitCount++;
                }

                progressArgs.HasCompleted = true;
                this.ReportError(BuildStep.Canceled, "BE0064", "BUILD CANCELLED BY USER");
                System.Diagnostics.Debug.WriteLine("Build process aborted\r\n");
            }
            catch(Exception ex)
            {
                BuilderException bex = ex as BuilderException;
                System.Diagnostics.Debug.WriteLine(ex);
                progressArgs.HasCompleted = true;

                do
                {
                    if(message != null)
                        message += "\r\n";

                    message += ex.Message;
                    ex = ex.InnerException;

                } while(ex != null);

                // NOTE: Message may contain format markers so pass it as a
                // format argument.
                if(bex != null)
                    this.ReportError(BuildStep.Failed, bex.ErrorCode, "{0}", message);
                else
                    this.ReportError(BuildStep.Failed, "BE0065", "BUILD FAILED: {0}", message);

                System.Diagnostics.Debug.WriteLine("Build process failed\r\n");
            }
            finally
            {
                if(currentProcess != null)
                    currentProcess.Dispose();

                try
                {
                    this.ExecutePlugIns(ExecutionBehaviors.Before);
                }
                catch(Exception ex)
                {
                    // Not much we can do at this point...
                    this.ReportProgress(ex.ToString());
                }

                try
                {
                    this.ExecutePlugIns(ExecutionBehaviors.After);

                    if(loadedPlugIns != null)
                        foreach(IPlugIn plugIn in loadedPlugIns.Values)
                            plugIn.Dispose();
                }
                catch(Exception ex)
                {
                    // Not much we can do at this point...
                    this.ReportProgress(ex.ToString());
                }
                finally
                {
                    this.GarbageCollect();

                    if(swLog != null)
                    {
                        swLog.WriteLine("</buildStep>\r\n</shfbBuild>");
                        swLog.Close();
                        swLog = null;
                    }

                    if(progressArgs.BuildStep == BuildStep.Completed && !project.KeepLogFile)
                        File.Delete(this.LogFilename);
                }
            }
        }