//===================================================================== /// <summary> /// This is used to load the settings for a .NET Framework version from an XML element /// </summary> /// <param name="framework">The XML element containing the settings</param> /// <returns>The new framework settings item</returns> internal static FrameworkSettings FromXml(XElement framework) { FrameworkSettings fs = new FrameworkSettings { Title = framework.Attribute("Title").Value, Platform = framework.Attribute("Platform").Value, Version = new Version(framework.Attribute("Version").Value), Redirect = (string)framework.Attribute("Redirect") }; foreach (var location in framework.Descendants("Location")) { fs.assemblyLocations.Add(AssemblyLocation.FromXml(location)); } return(fs); }
//===================================================================== /// <summary> /// Validate the documentation source information and copy the files to the working folder /// </summary> /// <exception cref="BuilderException">This is thrown if any of the information is invalid</exception> private void ValidateDocumentationSources() { List<string> commentsList = new List<string>(); Dictionary<string, MSBuildProject> projectDictionary = new Dictionary<string, MSBuildProject>(); HashSet<string> targetFrameworksSeen = new HashSet<string>(), targetFrameworkVersionsSeen = new HashSet<string>(); MSBuildProject projRef; XPathDocument testComments; XPathNavigator navComments; XmlCommentsFile comments; int fileCount; string workingPath, lastSolution = null; this.ReportProgress(BuildStep.ValidatingDocumentationSources, "Validating and copying documentation source information"); assembliesList = new Collection<string>(); referenceDictionary = new Dictionary<string, Tuple<string, string, List<KeyValuePair<string, string>>>>(); commentsFiles = new XmlCommentsFileCollection(); if(this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) return; // It's possible a plug-in might want to add or remove assemblies so we'll run them before checking // to see if the project has any. this.ExecutePlugIns(ExecutionBehaviors.Before); if(project.DocumentationSources.Count == 0) throw new BuilderException("BE0039", "The project does not have any documentation sources defined"); // Clone the project's references. These will be added to a build project later on so we'll note the // necessary information needed to create the reference in the future project. foreach(string refType in (new string[] { "Reference", "COMReference" })) foreach(ProjectItem reference in project.MSBuildProject.GetItems(refType)) referenceDictionary.Add(reference.EvaluatedInclude, Tuple.Create(reference.ItemType, reference.EvaluatedInclude, reference.Metadata.Select(m => new KeyValuePair<string, string>(m.Name, m.EvaluatedValue)).ToList())); // Convert project references to regular references that point to the output assembly. Project // references get built and we may not have enough info for that to happen successfully. As such, // we'll assume the project has already been built and that its target exists. foreach(ProjectItem reference in project.MSBuildProject.GetItems("ProjectReference")) { // Ignore references used only for MSBuild dependency determination var refOutput = reference.GetMetadata(ProjectElement.ReferenceOutputAssembly); if(refOutput != null && refOutput.EvaluatedValue.Equals("false", StringComparison.OrdinalIgnoreCase)) { this.ReportProgress("Ignoring reference to '{0}' which is only used for MSBuild dependency " + "determination", reference.EvaluatedInclude); continue; } using(projRef = new MSBuildProject(reference.EvaluatedInclude)) { projRef.SetConfiguration(project.Configuration, project.Platform, project.MSBuildOutDir); referenceDictionary.Add(projRef.AssemblyName, Tuple.Create("Reference", Path.GetFileNameWithoutExtension(projRef.AssemblyName), (new [] { new KeyValuePair<string, string>("HintPath", projRef.AssemblyName) }).ToList())); } } try { // For each source, make three passes: one for projects, one for assemblies and one for comments // files. Projects and comments files are optional but when all done, at least one assembly must // have been found. foreach(DocumentationSource ds in project.DocumentationSources) { fileCount = 0; this.ReportProgress("Source: {0}", ds.SourceFile); foreach(var sourceProject in DocumentationSource.Projects(ds.SourceFile, ds.IncludeSubFolders, !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration, !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform)) { // NOTE: This code in EntityReferenceWindow.IndexComments should be similar to this! // Solutions are followed by the projects that they contain if(sourceProject.ProjectFileName.EndsWith(".sln", StringComparison.OrdinalIgnoreCase)) { lastSolution = sourceProject.ProjectFileName; continue; } if(!projectDictionary.ContainsKey(sourceProject.ProjectFileName)) { // These are handled below this.ReportProgress(" Found project '{0}'", sourceProject.ProjectFileName); projRef = new MSBuildProject(sourceProject.ProjectFileName); // Use the project file configuration and platform properties if they are set. If not, // use the documentation source values. If they are not set, use the SHFB project settings. projRef.SetConfiguration( !String.IsNullOrEmpty(sourceProject.Configuration) ? sourceProject.Configuration : !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration, !String.IsNullOrEmpty(sourceProject.Platform) ? sourceProject.Platform : !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform, project.MSBuildOutDir); // Add Visual Studio solution macros if necessary if(lastSolution != null) projRef.SetSolutionMacros(lastSolution); projectDictionary.Add(sourceProject.ProjectFileName, projRef); } else this.ReportProgress(" Ignoring duplicate project file '{0}'", sourceProject.ProjectFileName); fileCount++; } foreach(string asmName in DocumentationSource.Assemblies(ds.SourceFile, ds.IncludeSubFolders)) { if(!assembliesList.Contains(asmName)) { // Assemblies are parsed in place by MRefBuilder so we don't have to do anything with // them here. this.ReportProgress(" Found assembly '{0}'", asmName); assembliesList.Add(asmName); } else this.ReportProgress(" Ignoring duplicate assembly file '{0}'", asmName); fileCount++; } foreach(string commentsName in DocumentationSource.CommentsFiles(ds.SourceFile, ds.IncludeSubFolders)) { if(!commentsList.Contains(commentsName)) { // These are handled below commentsList.Add(commentsName); } else this.ReportProgress(" Ignoring duplicate comments file '{0}'", commentsName); fileCount++; } if(fileCount == 0) this.ReportWarning("BE0006", "Unable to locate any documentation sources for '{0}' " + "(Configuration: {1} Platform: {2})", ds.SourceFile, !String.IsNullOrEmpty(ds.Configuration) ? ds.Configuration : project.Configuration, !String.IsNullOrEmpty(ds.Platform) ? ds.Platform : project.Platform); } // Parse projects for assembly, comments, and reference info if(projectDictionary.Count != 0) { this.ReportProgress("\r\nParsing project files"); foreach(MSBuildProject msbProject in projectDictionary.Values) { workingPath = msbProject.AssemblyName; if(!String.IsNullOrEmpty(workingPath)) { if(!File.Exists(workingPath)) throw new BuilderException("BE0040", "Project assembly does not exist: " + workingPath); if(!assembliesList.Contains(workingPath)) { // Assemblies are parsed in place by MRefBuilder so we don't have to do anything // with them here. this.ReportProgress(" Found assembly '{0}'", workingPath); assembliesList.Add(workingPath); } else this.ReportProgress(" Ignoring duplicate assembly file '{0}'", workingPath); } else throw new BuilderException("BE0067", String.Format(CultureInfo.CurrentCulture, "Unable to obtain assembly name from project file '{0}' using Configuration " + "'{1}', Platform '{2}'", msbProject.ProjectFile.FullPath, msbProject.ProjectFile.AllEvaluatedProperties.Last( p => p.Name == ProjectElement.Configuration).EvaluatedValue, msbProject.ProjectFile.AllEvaluatedProperties.Last( p => p.Name == ProjectElement.Platform).EvaluatedValue)); workingPath = msbProject.XmlCommentsFile; if(!String.IsNullOrEmpty(workingPath)) { if(!File.Exists(workingPath)) throw new BuilderException("BE0041", "Project XML comments file does not exist: " + workingPath); if(!commentsList.Contains(workingPath)) { // These are handled below commentsList.Add(workingPath); } else this.ReportProgress(" Ignoring duplicate comments file '{0}'", workingPath); } // Note the platforms seen and the highest framework version used targetFrameworksSeen.Add(msbProject.TargetFrameworkIdentifier); targetFrameworkVersionsSeen.Add(msbProject.TargetFrameworkVersion); // Clone the project's reference information msbProject.CloneReferenceInfo(referenceDictionary); } // If we saw multiple framework types in the projects, stop now. Due to the different // assemblies used, we cannot mix the project types within the same SHFB project. They will // need to be documented separately and can be merged using the Version Builder plug-in if // needed. if(targetFrameworksSeen.Count > 1) throw new BuilderException("BE0070", "Differing framework types were detected in the " + "documentation sources (i.e. .NET, Silverlight, Portable). Due to the different " + "sets of assemblies used, the different frameworks cannot be mixed within the same " + "documentation project. See the error number topic in the help file for details."); // If a project with a higher framework version was found, switch to that version now var projectFramework = FrameworkDictionary.AllFrameworks.FrameworkMatching( targetFrameworksSeen.First(), new Version(targetFrameworkVersionsSeen.Max(f => f)), true); if(frameworkSettings != projectFramework) { // If redirected and no suitable version was found, we can't go any further if(projectFramework == null) throw new BuilderException("BE0073", String.Format(CultureInfo.CurrentCulture, "A project with a different or higher framework version was found but that " + "version ({0} {1}) or a suitable redirected version was not found on this " + "system. The build cannot continue.", targetFrameworksSeen.First(), targetFrameworkVersionsSeen.Max(f => f))); this.ReportWarning("BE0007", "A project with a different or higher framework version " + "was found. Changing project FrameworkVersion property from '{0}' to '{1}' for " + "the build.", project.FrameworkVersion, projectFramework.Title); project.FrameworkVersion = projectFramework.Title; frameworkSettings = projectFramework; } } } finally { // Dispose of any MSBuild projects that we loaded foreach(var p in projectDictionary.Values) p.Dispose(); } if(assembliesList.Count == 0) throw new BuilderException("BE0042", "You must specify at least one documentation source in " + "the form of an assembly or a Visual Studio solution/project file"); // Log the references found, if any if(referenceDictionary.Count != 0) { this.ReportProgress("\r\nReferences to include (excluding framework assemblies):"); string[] keys = new string[referenceDictionary.Keys.Count]; referenceDictionary.Keys.CopyTo(keys, 0); Array.Sort(keys); // Filter out references related to the framework. MRefBuilder will resolve these // automatically. foreach(string key in keys) if(frameworkSettings.ContainsAssembly(key)) referenceDictionary.Remove(key); else this.ReportProgress(" {0}", key); if(referenceDictionary.Count == 0) this.ReportProgress(" None"); } if(commentsList.Count != 0) this.ReportProgress("\r\nCopying XML comments files"); // XML comments files are copied to the working folder in case they need to be fixed up foreach(string commentsName in commentsList) { workingPath = workingFolder + Path.GetFileName(commentsName); // Warn if there is a duplicate and copy the comments file to a unique name to preserve its // content. if(File.Exists(workingPath)) { workingPath = workingFolder + Guid.NewGuid().ToString("B"); this.ReportWarning("BE0063", "'{0}' matches a previously copied comments filename. The " + "duplicate will be copied to a unique name to preserve the comments it contains.", commentsName); } try { // Not all XML files found may be comments files. Ignore those that are not. testComments = new XPathDocument(commentsName); navComments = testComments.CreateNavigator(); if(navComments.SelectSingleNode("doc/members") == null) { this.ReportWarning("BE0005", "File '{0}' does not contain a 'doc/members' node and " + "will not be used as an XML comments file.", commentsName); continue; } } catch(Exception ex) { this.ReportWarning("BE0061", "File '{0}' could not be loaded and will not be used as an " + "XML comments file. Error: {1}", commentsName, ex.Message); continue; } File.Copy(commentsName, workingPath, true); File.SetAttributes(workingPath, FileAttributes.Normal); // Add the file to the XML comments file collection comments = new XmlCommentsFile(workingPath); // Fix up comments for CPP comments files? if(project.CppCommentsFixup) comments.FixupComments(); commentsFiles.Add(comments); this.ReportProgress(" {0} -> {1}", commentsName, workingPath); } if(commentsFiles.Count == 0) this.ReportWarning("BE0062", "No XML comments files found. The help file will not contain " + "any member comments."); this.ExecutePlugIns(ExecutionBehaviors.After); }
//===================================================================== /// <summary> /// 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); } } }
//===================================================================== /// <summary> /// This is used to load the settings for a .NET Framework version from an XML element /// </summary> /// <param name="framework">The XML element containing the settings</param> /// <returns>The new framework settings item</returns> internal static FrameworkSettings FromXml(XElement framework) { FrameworkSettings fs = new FrameworkSettings { Title = framework.Attribute("Title").Value, Platform = framework.Attribute("Platform").Value, Version = new Version(framework.Attribute("Version").Value), Redirect = (string)framework.Attribute("Redirect") }; foreach(var location in framework.Descendants("Location")) fs.assemblyLocations.Add(AssemblyLocation.FromXml(location)); return fs; }