/// <summary> /// Remove the item from the project /// </summary> public void RemoveFromProjectFile() { this.CheckProjectIsEditable(); projectFile.MSBuildProject.RemoveItem(item); projectFile = null; item = null; }
//===================================================================== /// <summary> /// Internal constructor /// </summary> /// <param name="isEnabled">The enabled state</param> /// <param name="configuration">The configuration</param> /// <param name="project">The owning project</param> internal PlugInConfiguration(bool isEnabled, string configuration, SandcastleProject project) : base(project) { enabled = isEnabled; config = String.IsNullOrWhiteSpace(configuration) ? "<configuration />" : configuration; }
/// <inheritdoc /> protected override bool BindControlValue(string propertyName) { SandcastleProject currentProject = null; #if !STANDALONEGUI if (this.ProjectMgr != null) { currentProject = ((SandcastleBuilderProjectNode)this.ProjectMgr).SandcastleProject; } #else currentProject = this.CurrentProject; #endif ucPlugInPropertiesPageContent.Project = currentProject; if (currentProject == null) { ucPlugInPropertiesPageContent.LoadPlugInSettings(null, null); } else { ucPlugInPropertiesPageContent.LoadPlugInSettings(currentProject.Filename, currentProject.ComponentSearchPaths); } return(true); }
/// <summary> /// This method is used by the Sandcastle Help File Builder to let the plug-in perform its own /// configuration. /// </summary> /// <param name="project">A reference to the active project</param> /// <param name="currentConfig">The current configuration XML fragment</param> /// <returns>A string containing the new configuration XML fragment</returns> /// <remarks>The configuration data will be stored in the help file builder project</remarks> public string ConfigurePlugIn(SandcastleProject project, string currentConfig) { MessageBox.Show("This plug-in has no configurable settings", "Manual Visibility/API Filter Plug-In", MessageBoxButtons.OK, MessageBoxIcon.Information); return(currentConfig); }
/// <inheritdoc /> public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { SandcastleProject project = value as SandcastleProject; int count; if (project == null || destinationType != typeof(string)) { return(base.ConvertTo(context, culture, value, destinationType)); } try { count = project.GetUserDefinedProperties().Count; } catch (Exception ex) { return("(Error: " + ex.Message + ")"); } if (count == 0) { return("(None)"); } return(String.Format(culture, "{0} user-defined project properties", count)); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="project">The current project</param> /// <param name="configuration">The current configuration element</param> public AdditionalReferenceLinksConfigDlg(SandcastleProject project, XElement configuration) { InitializeComponent(); cboHtmlSdkLinkType.ItemsSource = cboWebsiteSdkLinkType.ItemsSource = (new Dictionary <string, string> { { HtmlSdkLinkType.Msdn.ToString(), "Links to online help topics" }, { HtmlSdkLinkType.None.ToString(), "No SDK links" } }).ToList(); cboMSHelpViewerSdkLinkType.ItemsSource = (new Dictionary <string, string> { { MSHelpViewerSdkLinkType.Msdn.ToString(), "Links to online help topics" }, { MSHelpViewerSdkLinkType.Id.ToString(), "ID links within the collection" }, { MSHelpViewerSdkLinkType.None.ToString(), "No SDK links" } }).ToList(); this.project = project ?? throw new ArgumentNullException(nameof(project)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); if (!configuration.IsEmpty) { // Load the current settings foreach (var reference in configuration.Descendants("target")) { lbReferences.Items.Add(ReferenceLinkSettings.FromXml(project, reference)); } } btnDeleteReferenceProject.IsEnabled = grpReferenceProps.IsEnabled = lbReferences.Items.Count != 0; if (lbReferences.Items.Count != 0) { lbReferences.SelectedIndex = 0; } }
//===================================================================== /// <summary> /// This constructor is used to wrap an existing reference /// </summary> /// <param name="project">The project that owns the reference</param> /// <param name="existingItem">The existing reference</param> /// <overloads>There are two overloads for the constructor</overloads> internal ProjectReferenceItem(SandcastleProject project, ProjectItem existingItem) : base(project, existingItem) { projectPath = new FilePath(this.Include, this.Project); projectPath.PersistablePathChanging += projectPath_PersistablePathChanging; this.GetProjectMetadata(false); this.Include = projectPath.PersistablePath; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="project">The project file reference</param> public UserDefinedPropertyEditorDlg(SandcastleProject project) { PropertyItem propItem; InitializeComponent(); this.Project = project; this.UserDefinedProperties = new Collection<PropertyItem>(); lbProperties.Sorted = true; try { foreach(BuildProperty prop in this.Project.GetUserDefinedProperties()) { propItem = new PropertyItem(this, prop); this.UserDefinedProperties.Add(propItem); lbProperties.Items.Add(propItem); } } catch(Exception ex) { MessageBox.Show("Unable to load user-defined properties. " + "Error " + ex.Message, Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); } lbProperties.Sorted = false; if(lbProperties.Items.Count == 0) pgProps.Enabled = false; else lbProperties.SelectedIndex = 0; }
/// <summary> /// This method is used by the Sandcastle Help File Builder to let the plug-in perform its own configuration. /// </summary> /// <param name="project">A reference to the active project</param> /// <param name="currentConfig">The current configuration XML fragment</param> /// <returns>A string containing the new configuration XML fragment</returns> /// <remarks>The configuration data will be stored in the help file builder project.</remarks> public string ConfigurePlugIn(SandcastleProject project, string currentConfig) { //TODO przeniesc calosc do plugin. Form ConfigurationForm = new PluginConfig(currentConfig); ConfigurationForm.Show(); //if ( currentConfig.ToLower().ToString() == "<configuration />" ) //{ // StringBuilder sbWriteConfig = new StringBuilder(); // sbWriteConfig.AppendLine(); // XmlWriterSettings settingWriter = new XmlWriterSettings(); // settingWriter.OmitXmlDeclaration = true; // XmlWriter xmlWriter = XmlWriter.Create( sbWriteConfig, settingWriter ); // xmlWriter.WriteStartElement( "configuration" ); // xmlWriter.WriteElementString( "website", Properties.Settings.Default.website ); // xmlWriter.WriteEndElement(); // xmlWriter.Flush(); // xmlWriter.Close(); // sbWriteConfig.AppendLine(); // currentConfig = sbWriteConfig.ToString(); //} return(currentConfig); }
/// <summary> /// This is called to build a project /// </summary> /// <param name="project">The project to build</param> /// <returns>Returns true if successful, false if not</returns> private static bool BuildProject(SandcastleProject project) { lastBuildStep = BuildStep.None; currentProject = project; Console.WriteLine("\nBuilding {0}", project.Filename); try { buildProcess = new BuildProcess(currentProject); buildProcess.BuildStepChanged += new EventHandler <BuildProgressEventArgs>( buildProcess_BuildStepChanged); buildProcess.BuildProgress += new EventHandler <BuildProgressEventArgs>( buildProcess_BuildProgress); // Since this is a console app, we'll run it directly rather // than in a background thread. buildProcess.Build(); } catch (Exception ex) { Console.WriteLine("Fatal error, unable to compile " + "project '{0}': {1}", project.Filename, ex.ToString()); } return(lastBuildStep == BuildStep.Completed); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="currentProject">The current project</param> /// <param name="currentConfig">The current XML configuration /// XML fragment</param> public WildcardReferencesConfigDlg(SandcastleProject currentProject, string currentConfig) { InitializeComponent(); project = currentProject; lnkProjectSite.Links[0].LinkData = "https://GitHub.com/EWSoftware/SHFB"; lbReferences.DisplayMember = lbReferences.ValueMember = "ListDescription"; items = new WildcardReferenceSettingsCollection(); // Load the current settings config = XElement.Parse(currentConfig); if (!config.IsEmpty) { items.FromXml(project, config); } if (items.Count == 0) { pgProps.Enabled = btnDelete.Enabled = false; } else { // Binding the collection to the list box caused some odd problems with the property grid so // we'll add the items to the list box directly. foreach (WildcardReferenceSettings rl in items) { lbReferences.Items.Add(rl); } lbReferences.SelectedIndex = 0; } }
//===================================================================== /// <summary> /// Internal constructor /// </summary> /// <param name="itemName">The namespace's name</param> /// <param name="documented">The flag indicating whether or not the /// namespace is to be documented.</param> /// <param name="summaryText">The summary text</param> /// <param name="project">The owning project</param> internal NamespaceSummaryItem(string itemName, bool documented, string summaryText, SandcastleProject project) : base(project) { name = itemName; summary = summaryText; isDocumented = documented; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="currentProject">The current project</param> /// <param name="currentConfig">The current XML configuration XML fragment</param> public BindingRedirectResolverConfigDlg(SandcastleProject currentProject, string currentConfig) { XPathNavigator navigator, root; string useGac; InitializeComponent(); project = currentProject; lnkProjectSite.Links[0].LinkData = "https://GitHub.com/EWSoftware/SHFB"; items = new BindingRedirectSettingsCollection(); // Load the current settings config = new XmlDocument(); config.LoadXml(currentConfig); navigator = config.CreateNavigator(); root = navigator.SelectSingleNode("configuration"); useGac = root.GetAttribute("useGAC", String.Empty); if (Boolean.TryParse(useGac, out bool value)) { chkUseGAC.Checked = value; } if (!root.IsEmptyElement) { items.FromXml(project, root); } if (items.Count == 0) { pgProps.Enabled = btnDelete.Enabled = false; } else { // Binding the collection to the list box caused some odd problems with the property grid so // we'll add the items to the list box directly. foreach (BindingRedirectSettings brs in items) { lbRedirects.Items.Add(brs); } lbRedirects.SelectedIndex = 0; } foreach (XPathNavigator nav in root.Select("ignoreIfUnresolved/assemblyIdentity/@name")) { lbIgnoreIfUnresolved.Items.Add(nav.Value); } if (lbIgnoreIfUnresolved.Items.Count == 0) { lbIgnoreIfUnresolved.Items.Add("BusinessObjects.Licensing.KeycodeDecoder"); lbIgnoreIfUnresolved.Items.Add("Microsoft.VisualStudio.TestTools.UITest.Playback"); } lbIgnoreIfUnresolved.SelectedIndex = 0; }
/// <summary> /// Launch the Launch Help Viewer 2.x Content Manager for interactive use based on the current project's /// settings. /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void LaunchContentMgrExecuteHandler(object sender, EventArgs e) { Version version; try { SandcastleProject project = CurrentSandcastleProject; if (project != null) { if (project.CatalogName == "VisualStudio11") { version = new Version(2, 0); } else { version = new Version(2, 1); } HelpLibraryManager hlm = new HelpLibraryManager(version); hlm.LaunchInteractive(String.Format(CultureInfo.InvariantCulture, "/catalogName \"{0}\" /locale {1} /manage", project.CatalogName, project.Language.Name)); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); Utility.ShowMessageBox(OLEMSGICON.OLEMSGICON_CRITICAL, "Unable to launch Help Viewer 2.x " + "Content Manager. Reason:\r\n{0}\r\n\r\nIs the catalog name correct in the project?", ex.Message); } }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="oldProjectFile">The old project filename</param> /// <param name="folder">The folder in which to place the new project and its related files. This /// cannot be the same folder as the old project file.</param> /// <overloads>There are two overloads for the constructor</overloads> protected ConvertToMSBuildFormat(string oldProjectFile, string folder) { string projectFilename; oldProject = oldProjectFile; oldFolder = FolderPath.TerminatePath(Path.GetDirectoryName(Path.GetFullPath(oldProjectFile))); projectFolder = FolderPath.TerminatePath(folder); if (folder == oldFolder) { throw new ArgumentException("The new project folder cannot be the same as the old project " + "file's folder", "folder"); } if (!Directory.Exists(projectFolder)) { Directory.CreateDirectory(projectFolder); } projectFilename = Path.Combine(projectFolder, Path.GetFileNameWithoutExtension(oldProjectFile) + ".shfbproj"); if (File.Exists(projectFilename)) { File.Delete(projectFilename); } project = new SandcastleProject(projectFilename, false); }
/// <inheritdoc /> public bool EditConfiguration(SandcastleProject project, XElement configuration) { using (var dlg = new WindowsFormsExampleConfigDlg(configuration)) { return(dlg.ShowDialog() == DialogResult.OK); } }
/// <inheritdoc /> protected override void LoadFile(string fileName) { SandcastleProject project = null; bool disposeOfProject = false; resourceItemFilename = fileName; try { // Get the current project so that the editor knows what presentation style items to load project = SandcastleBuilderPackage.CurrentSandcastleProject; if (project == null) { // If there is no current project, create a dummy project to use disposeOfProject = true; project = new SandcastleProject("__TempProject__.shfbproj", false, false); } base.UIControl.LoadResourceItemsFile(fileName, project); } finally { if (disposeOfProject) { project.Dispose(); } } }
/// <summary> /// This constructor is used to create a new build item and add it to the project /// </summary> /// <param name="project">The project that will own the item</param> /// <param name="itemType">The type of build item to create</param> /// <param name="itemPath">The path to the item. This can be relative or absolute and may contain /// variable references.</param> protected ProjectElement(SandcastleProject project, string itemType, string itemPath) { if (project == null) { throw new ArgumentNullException("project"); } if (String.IsNullOrEmpty(itemPath)) { throw new ArgumentException("Cannot be null or empty", "itemPath"); } if (String.IsNullOrEmpty(itemType)) { throw new ArgumentException("Cannot be null or empty", "itemType"); } projectFile = project; if (itemType == Utils.BuildAction.Folder.ToString() && itemPath[itemPath.Length - 1] != '\\') { itemPath += @"\"; } item = project.MSBuildProject.AddItem(itemType, itemPath)[0]; projectFile.MSBuildProject.ReevaluateIfNecessary(); }
/// <inheritdoc /> public string ConfigurePlugIn(SandcastleProject project, string currentConfig) { MessageBox.Show("This plug-in has no configurable settings", "Lightweight Website Style Plug-In", MessageBoxButtons.OK, MessageBoxIcon.Information); return(currentConfig); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="project">The current project</param> /// <param name="configuration">The current configuration element</param> public VersionBuilderConfigDlg(SandcastleProject project, XElement configuration) { InitializeComponent(); this.project = project ?? throw new ArgumentNullException(nameof(project)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); if (!configuration.IsEmpty) { var currentProject = configuration.Element("currentProject"); if (currentProject != null) { txtLabel.Text = currentProject.Attribute("label").Value; txtVersion.Text = currentProject.Attribute("version").Value; chkRipOldAPIs.IsChecked = (bool)currentProject.Attribute("ripOldApis"); } // Load the current settings foreach (var reference in configuration.Descendants("version")) { lbVersionInfo.Items.Add(VersionSettings.FromXml(project, reference)); } } btnDeleteProject.IsEnabled = grpVersionInfoProps.IsEnabled = lbVersionInfo.Items.Count != 0; if (lbVersionInfo.Items.Count != 0) { lbVersionInfo.SelectedIndex = 0; } }
/// <summary> /// This method is used by the Sandcastle Help File Builder to let the plug-in perform its own /// configuration. /// </summary> /// <param name="project">A reference to the active project</param> /// <param name="currentConfig">The current configuration XML fragment</param> /// <returns>A string containing the new configuration XML fragment</returns> /// <remarks>The configuration data will be stored in the help file builder project</remarks> public string ConfigurePlugIn(SandcastleProject project, string currentConfig) { MessageBox.Show("This plug-in has no configurable settings", "IntelliSense Only Plug-In", MessageBoxButtons.OK, MessageBoxIcon.Information); return(currentConfig); }
/// <summary> /// This constructor is used to create a new build item and add it to /// the project. /// </summary> /// <param name="project">The project that will own the item</param> /// <param name="itemType">The type of build item to create</param> /// <param name="itemPath">The path to the item. This can be relative /// or absolute and may contain variable references.</param> internal ProjectElement(SandcastleProject project, string itemType, string itemPath) { if (project == null) { throw new ArgumentNullException("project"); } if (String.IsNullOrEmpty(itemPath)) { throw new ArgumentException("Cannot be null or empty", "itemPath"); } if (String.IsNullOrEmpty(itemType)) { throw new ArgumentException("Cannot be null or empty", "itemType"); } projectFile = project; this.CheckProjectIsEditable(); if (itemType == Utils.BuildAction.Folder.ToString() && itemPath[itemPath.Length - 1] != '\\') { itemPath += @"\"; } item = project.MSBuildProject.AddItem(itemType, itemPath)[0]; projectFile.MarkAsDirty(); }
/// <summary> /// This method is used by the Sandcastle Help File Builder to let the plug-in perform its own /// configuration. /// </summary> /// <param name="project">A reference to the active project</param> /// <param name="currentConfig">The current configuration XML fragment</param> /// <returns>A string containing the new configuration XML fragment</returns> /// <remarks>The configuration data will be stored in the help file builder project</remarks> public string ConfigurePlugIn(SandcastleProject project, string currentConfig) { MessageBox.Show("This plug-in has no configurable settings", "Script# Reflection File Fixer Plug-In", MessageBoxButtons.OK, MessageBoxIcon.Information); return(currentConfig); }
/// <summary> /// This method can be used by plug-ins to merge content from another Sandcastle Help File Builder /// project file. /// </summary> /// <param name="project">The project file from which to merge content</param> /// <remarks>Auto-generated content can be added to a temporary SHFB project and then added to the /// current project's content at build time using this method. Such content cannot always be added to /// the project being built as it may alter the underlying MSBuild project which is not wanted.</remarks> public void MergeContentFrom(SandcastleProject project) { var otherImageFiles = new ImageReferenceCollection(project); var otherCodeSnippetFiles = new FileItemCollection(project, BuildAction.CodeSnippets); var otherTokenFiles = new FileItemCollection(project, BuildAction.Tokens); var otherContentLayoutFiles = new FileItemCollection(project, BuildAction.ContentLayout); foreach (var image in otherImageFiles) { imageFiles.Add(image); } foreach (var snippets in otherCodeSnippetFiles) { codeSnippetFiles.Add(snippets); } foreach (var tokens in otherTokenFiles) { tokenFiles.Add(tokens); } foreach (FileItem file in otherContentLayoutFiles) { topics.Add(new TopicCollection(file)); } }
/// <inheritdoc /> protected override bool BindControlValue(string propertyName) { SandcastleProject currentProject = null; #if !STANDALONEGUI if (this.ProjectMgr == null) { ucBuildPropertiesPageContent.LoadBuildFormatInfo(null, null); return(false); } currentProject = ((SandcastleBuilderProjectNode)this.ProjectMgr).SandcastleProject; #else if (this.CurrentProject == null) { ucBuildPropertiesPageContent.LoadBuildFormatInfo(null, null); return(false); } currentProject = this.CurrentProject; #endif if (propertyName == "FrameworkVersion") { ucBuildPropertiesPageContent.LoadReflectionDataSetInfo(currentProject); return(false); } // Get the selected help file formats if (propertyName == "HelpFileFormat") { HelpFileFormats formats; ProjectProperty projProp = currentProject.MSBuildProject.GetProperty("HelpFileFormat"); if (projProp == null || !Enum.TryParse <HelpFileFormats>(projProp.UnevaluatedValue, out formats)) { formats = HelpFileFormats.HtmlHelp1; } ucBuildPropertiesPageContent.SelectedHelpFileFormats = formats; return(true); } // Load the presentation styles and syntax filters if (propertyName == "SyntaxFilters") { ucBuildPropertiesPageContent.LoadBuildFormatInfo(currentProject.Filename, new[] { currentProject.ComponentPath, Path.GetDirectoryName(currentProject.Filename) }); return(true); } // This is loaded along with the syntax filters after the components are determined if (propertyName == "PresentationStyle") { return(true); } return(false); }
//===================================================================== // Build methods /// <summary> /// This kicks off the build process in a background thread /// </summary> private void BuildConceptualTopics() { PlugInConfiguration pc; string tempPath; try { // Set up the project using information from the current project tempProject = new SandcastleProject(currentProject, true); // The temporary project resides in the same folder as the // current project (by filename only, it isn't saved) to // maintain relative paths. However, build output is stored // in a temporary folder and it keeps the intermediate files. tempProject.CleanIntermediates = false; tempPath = Path.GetTempFileName(); File.Delete(tempPath); tempPath = Path.Combine(Path.GetDirectoryName(tempPath), "SHFBPartialBuild"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } tempProject.OutputPath = tempPath; // Force website output so that we know where to find the output tempProject.HelpFileFormat = HelpFileFormat.Website; // Add the Additional Content Only plug-in or update the it if // already there to only do a preview build. if (tempProject.PlugInConfigurations.TryGetValue("Additional Content Only", out pc)) { pc.Enabled = true; pc.Configuration = "<configuration previewBuild='true' />"; } else { tempProject.PlugInConfigurations.Add("Additional Content Only", true, "<configuration previewBuild='true' />"); } buildProcess = new BuildProcess(tempProject); buildProcess.BuildStepChanged += buildProcess_BuildStepChanged; buildThread = new Thread(new ThreadStart(buildProcess.Build)); buildThread.Name = "Help file builder thread"; buildThread.IsBackground = true; buildThread.Start(); } catch (Exception ex) { Directory.SetCurrentDirectory(Path.GetDirectoryName(currentProject.Filename)); System.Diagnostics.Debug.WriteLine(ex.ToString()); MessageBox.Show("Unable to build project to preview topic. " + "Error: " + ex.Message, Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// This gets all possible content files from the project and attempts /// to match them to the topics in the collection by ID. /// </summary> public void MatchProjectFilesToTopics() { SandcastleProject project = fileItem.ProjectElement.Project; FileItem topicItem; TopicFile topicFile; string ext, none = BuildAction.None.ToString(), content = BuildAction.Content.ToString(); foreach (ProjectItem item in project.MSBuildProject.AllEvaluatedItems) { if (item.ItemType == none || item.ItemType == content) { ext = Path.GetExtension(item.EvaluatedInclude).ToLowerInvariant(); if (ext == ".aml" || ext == ".htm" || ext == ".html" || ext == ".topic") { topicItem = new FileItem(new ProjectElement(project, item)); topicFile = new TopicFile(topicItem); if (topicFile.Id != null) { this.SetTopic(topicFile); } } } } }
/// <summary> /// Try to load information about all available framework reflection data sets /// </summary> /// <param name="currentProject">The current Sandcastle project</param> public void LoadReflectionDataSetInfo(SandcastleProject currentProject) { if (reflectionDataSets != null && currentProject != null && currentProject.Filename != lastProjectName) { return; } lastProjectName = currentProject?.Filename; try { Mouse.OverrideCursor = Cursors.Wait; if (currentProject != null) { reflectionDataSets = new ReflectionDataSetDictionary(currentProject.ComponentSearchPaths); } else { reflectionDataSets = new ReflectionDataSetDictionary(null); } } catch (Exception ex) { Debug.WriteLine(ex.ToString()); MessageBox.Show("Unexpected error loading reflection data set info: " + ex.Message, Constants.AppName, MessageBoxButton.OK, MessageBoxImage.Error); } finally { Mouse.OverrideCursor = null; } cboFrameworkVersion.Items.Clear(); if (reflectionDataSets.Keys.Count == 0) { imgFrameworkWarning.Visibility = Visibility.Visible; MessageBox.Show("No valid reflection data sets found", Constants.AppName, MessageBoxButton.OK, MessageBoxImage.Information); reflectionDataSets.Add(ReflectionDataSetDictionary.DefaultFrameworkTitle, new ReflectionDataSet { Title = ReflectionDataSetDictionary.DefaultFrameworkTitle }); } else { imgFrameworkWarning.Visibility = Visibility.Hidden; foreach (string dataSetName in reflectionDataSets.Keys.OrderBy(k => k)) { cboFrameworkVersion.Items.Add(dataSetName); } cboFrameworkVersion.SelectedItem = ReflectionDataSetDictionary.DefaultFrameworkTitle; } }
//===================================================================== /// <summary> /// Internal constructor /// </summary> /// <param name="itemName">The namespace's name</param> /// <param name="isGroup">This indicates whether or not the namespace is a group namespace</param> /// <param name="isDocumented">This indicates whether or not the namespace is to be documented</param> /// <param name="summaryText">The summary text</param> /// <param name="project">The owning project</param> internal NamespaceSummaryItem(string itemName, bool isGroup, bool isDocumented, string summaryText, SandcastleProject project) : base(project) { this.IsGroup = isGroup; this.name = itemName; this.summary = summaryText; this.isDocumented = isDocumented; }
/// <summary> /// Remove the item from the project /// </summary> public void RemoveFromProjectFile() { projectFile.MSBuildProject.RemoveItem(item); projectFile.MSBuildProject.ReevaluateIfNecessary(); projectFile = null; item = null; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="project">The project from which to load the settings</param> public ConceptualContentSettings(SandcastleProject project) { imageFiles = project.ImagesReferences.ToList(); codeSnippetFiles = project.ContentFiles(BuildAction.CodeSnippets).OrderBy(f => f.LinkPath).ToList(); tokenFiles = project.ContentFiles(BuildAction.Tokens).OrderBy(f => f.LinkPath).ToList(); contentLayoutFiles = project.ContentFiles(BuildAction.ContentLayout).ToList(); topics = project.ContentFiles(BuildAction.ContentLayout).Select(file => new TopicCollection(file)).ToList(); }
//===================================================================== /// <summary> /// Internal constructor /// </summary> /// <param name="isEnabled">The enabled state</param> /// <param name="configuration">The configuration</param> /// <param name="project">The owning project</param> internal BuildComponentConfiguration(bool isEnabled, string configuration, SandcastleProject project) : base(project) { if(String.IsNullOrWhiteSpace(configuration)) throw new ArgumentException("A configuration value is required", "configuration"); enabled = isEnabled; config = configuration; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="project">The project from which to load the settings</param> public ConceptualContentSettings(SandcastleProject project) { imageFiles = new ImageReferenceCollection(project); codeSnippetFiles = new FileItemCollection(project, BuildAction.CodeSnippets); tokenFiles = new FileItemCollection(project, BuildAction.Tokens); contentLayoutFiles = new FileItemCollection(project, BuildAction.ContentLayout); topics = new Collection<TopicCollection>(); foreach(FileItem file in contentLayoutFiles) topics.Add(new TopicCollection(file)); }
//===================================================================== /// <summary> /// Internal constructor /// </summary> /// <param name="isEnabled">The enabled state</param> /// <param name="configuration">The configuration</param> /// <param name="project">The owning project</param> internal BuildComponentConfiguration(bool isEnabled, string configuration, SandcastleProject project) : base(project) { enabled = isEnabled; if(String.IsNullOrEmpty(configuration)) config = "<configuration />"; else config = configuration; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="currentBuild">The current build for which to perform substitution tag replacement</param> public SubstitutionTagReplacement(BuildProcess currentBuild) { this.currentBuild = currentBuild; sandcastleProject = currentBuild.CurrentProject; msbuildProject = sandcastleProject.MSBuildProject; presentationStyle = currentBuild.PresentationStyle; replacementValue = new StringBuilder(10240); fieldMatchEval = new MatchEvaluator(OnFieldMatch); // Get the substitution tag methods so that we can invoke them. The dictionary keys are the method // names and are case-insensitive. Substitution tag methods take no parameters and return a value // that is convertible to a string. methodCache = this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Where( m => m.GetCustomAttribute(typeof(SubstitutionTagAttribute)) != null).ToDictionary( m => m.Name, m => m, StringComparer.OrdinalIgnoreCase); }
//===================================================================== /// <summary> /// This is used to execute the task and perform the build /// </summary> /// <returns>True on success or false on failure.</returns> public override bool Execute() { Project msBuildProject = null; ProjectInstance projectInstance = null; bool removeProjectWhenDisposed = false; string line; // If canceled already, just return if(buildCancelled) return false; try { if(!this.AlwaysLoadProject) { // Use the current project if possible. This is preferable as we can make use // of command line property overrides and any other user-defined properties. this.ProjectFile = Path.GetFullPath(this.ProjectFile); // This collection is new to the MSBuild 4.0 API. If you load a project, it appears // in the collection. However, if you run MSBuild.exe, it doesn't put the project in // this collection. As such, we must still resort to reflection to get the executing // project. I'm leaving this here in case that ever changes as this is preferable to // using Reflection to get at the current project. var matchingProjects = ProjectCollection.GlobalProjectCollection.GetLoadedProjects( this.ProjectFile); if(matchingProjects.Count != 0) { if(matchingProjects.Count != 1) Log.LogWarning(null, "BHT0004", "BHT0004", "SHFB", 0, 0, 0, 0, "Multiple matching " + "projects were found. Only the first one found will be built."); msBuildProject = matchingProjects.First(); } else projectInstance = this.GetCurrentProjectInstance(); } } catch(Exception ex) { // Ignore exceptions but issue a warning and fall back to using // the passed project filename instead. Log.LogWarning(null, "BHT0001", "BHT0001", "SHFB", 0, 0, 0, 0, "Unable to get executing " + "project: {0}. The specified project will be loaded but command line property " + "overrides will be ignored.", ex.Message); } try { if(msBuildProject == null) { removeProjectWhenDisposed = true; if(projectInstance != null) { msBuildProject = new Project(projectInstance.ToProjectRootElement()); // ToProjectRootElement() will not add properties in the global collection to the // project. One problem with this is that command line overrides get missed. As such, // we'll add them back to the project as long as they are not reserved names and are not // there already. foreach(var p in projectInstance.GlobalProperties) if(!SandcastleProject.restrictedProps.Contains(p.Key) && !msBuildProject.AllEvaluatedProperties.Any(ep => ep.Name == p.Key)) msBuildProject.SetProperty(p.Key, p.Value); msBuildProject.FullPath = this.ProjectFile; } else { if(!File.Exists(this.ProjectFile)) throw new BuilderException("BHT0003", "The specified project file does not exist: " + this.ProjectFile); Log.LogWarning(null, "BHT0001", "BHT0001", "SHFB", 0, 0, 0, 0, "Unable to get " + "executing project: Unable to obtain matching project from the global " + "collection. The specified project will be loaded but command line property " + "overrides will be ignored."); // Create the project and set the configuration and platform options msBuildProject = new Project(this.ProjectFile); } msBuildProject.SetGlobalProperty(ProjectElement.Configuration, this.Configuration); msBuildProject.SetGlobalProperty(ProjectElement.Platform, this.Platform); // Override the OutDir property if defined for Team Build. Ignore ".\" as that's our default. if(!String.IsNullOrEmpty(this.OutDir) && this.OutDir != @".\") msBuildProject.SetGlobalProperty(ProjectElement.OutDir, this.OutDir); msBuildProject.ReevaluateIfNecessary(); } // Associate the MSBuild project with a SHFB project instance and build it using(sandcastleProject = new SandcastleProject(msBuildProject)) { buildProcess = new BuildProcess(sandcastleProject); buildProcess.BuildStepChanged += buildProcess_BuildStepChanged; buildProcess.BuildProgress += buildProcess_BuildProgress; // Since this is an MSBuild task, we'll run it directly rather than in a background thread Log.LogMessage(MessageImportance.High, "Building {0}", msBuildProject.FullPath); buildProcess.Build(); } } catch(Exception ex) { Log.LogError(null, "BHT0002", "BHT0002", "SHFB", 0, 0, 0, 0, "Unable to build project '{0}': {1}", msBuildProject.FullPath, ex); } finally { // If we loaded it, we must unload it. If not, it is cached and may cause problems later. if(removeProjectWhenDisposed && msBuildProject != null) { ProjectCollection.GlobalProjectCollection.UnloadProject(msBuildProject); ProjectCollection.GlobalProjectCollection.UnloadProject(msBuildProject.Xml); } } if(this.DumpLogOnFailure && lastBuildStep == BuildStep.Failed) using(StreamReader sr = new StreamReader(buildProcess.LogFilename)) { Log.LogMessage(MessageImportance.High, "Log Content:"); do { line = sr.ReadLine(); // Don't output the XML elements, just the text if(line != null && (line.Trim().Length == 0 || line.Trim()[0] != '<')) Log.LogMessage(MessageImportance.High, line); } while(line != null); } return (lastBuildStep == BuildStep.Completed); }
/// <summary> /// Constructor /// </summary> /// <param name="oldProjectFile">The old project filename</param> /// <param name="newProject">The new project into which the converted elements are inserted</param> protected ConvertToMSBuildFormat(string oldProjectFile, SandcastleProject newProject) { oldProject = oldProjectFile; oldFolder = FolderPath.TerminatePath(Path.GetDirectoryName(Path.GetFullPath(oldProjectFile))); projectFolder = FolderPath.TerminatePath(Path.GetDirectoryName(newProject.Filename)); if(projectFolder == oldFolder) throw new ArgumentException("The new project folder cannot be the same as the old project " + "file's folder", "newProject"); project = new SandcastleProject(newProject.MSBuildProject); }
/// <summary> /// Constructor /// </summary> /// <param name="buildProject">The project to build</param> /// <param name="partialBuild">Pass true to perform a partial build</param> public BuildProcess(SandcastleProject buildProject, bool partialBuild) : this(buildProject) { isPartialBuild = partialBuild; }
/// <summary> /// This method can be used by plug-ins to merge content from another Sandcastle Help File Builder /// project file. /// </summary> /// <param name="project">The project file from which to merge content</param> /// <remarks>Auto-generated content can be added to a temporary SHFB project and then added to the /// current project's content at build time using this method. Such content cannot always be added to /// the project being built as it may alter the underlying MSBuild project which is not wanted.</remarks> public void MergeContentFrom(SandcastleProject project) { var otherImageFiles = new ImageReferenceCollection(project); var otherCodeSnippetFiles = new FileItemCollection(project, BuildAction.CodeSnippets); var otherTokenFiles = new FileItemCollection(project, BuildAction.Tokens); var otherContentLayoutFiles = new FileItemCollection(project, BuildAction.ContentLayout); foreach(var image in otherImageFiles) imageFiles.Add(image); foreach(var snippets in otherCodeSnippetFiles) codeSnippetFiles.Add(snippets); foreach(var tokens in otherTokenFiles) tokenFiles.Add(tokens); foreach(FileItem file in otherContentLayoutFiles) topics.Add(new TopicCollection(file)); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="buildProject">The project to build</param> /// <overloads>There are two overloads for the constructor.</overloads> public BuildProcess(SandcastleProject buildProject) { // If the project isn't using final values suitable for the build, // create a new project that is using final values. if(buildProject.UsingFinalValues) project = buildProject; else project = new SandcastleProject(buildProject, true); // Save a copy of the project filename. If using a temporary // project, it won't match the passed project's name. originalProjectName = buildProject.Filename; apiTocOrder = -1; apiTocParentId = rootContentContainerId = String.Empty; progressArgs = new BuildProgressEventArgs(); fieldMatchEval = new MatchEvaluator(OnFieldMatch); contentMatchEval = new MatchEvaluator(OnContentMatch); linkMatchEval = new MatchEvaluator(OnLinkMatch); codeBlockMatchEval = new MatchEvaluator(OnCodeBlockMatch); excludeElementEval = new MatchEvaluator(OnExcludeElement); help1Files = new Collection<string>(); help2Files = new Collection<string>(); helpViewerFiles = new Collection<string>(); websiteFiles = new Collection<string>(); helpFormatOutputFolders = new Collection<string>(); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="oldProjectFile">The old project filename</param> /// <param name="folder">The folder in which to place the new project /// and its related files. This cannot be the same folder as the /// old project file.</param> protected ConvertToMSBuildFormat(string oldProjectFile, string folder) { string projectFilename; oldProject = oldProjectFile; oldFolder = Path.GetDirectoryName(Path.GetFullPath( oldProjectFile)); projectFolder = FolderPath.TerminatePath(folder); oldFolder = FolderPath.TerminatePath(oldFolder); if(folder == oldFolder) throw new ArgumentException("The new project folder cannot " + "be the same as the old project file's folder", "folder"); if(!Directory.Exists(projectFolder)) Directory.CreateDirectory(projectFolder); projectFilename = Path.Combine(projectFolder, Path.GetFileNameWithoutExtension(oldProjectFile) + ".shfbproj"); if(File.Exists(projectFilename)) File.Delete(projectFilename); project = new SandcastleProject(projectFilename, false); }
/// <summary> /// Shut down the build process thread and clean up on exit /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void ApiFilterEditorDlg_FormClosing(object sender, FormClosingEventArgs e) { if(cancellationTokenSource != null && this.DialogResult != DialogResult.Cancel) { if(MessageBox.Show("A build is currently taking place to obtain API information. Do you want " + "to abort it and close this form?", Constants.AppName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { e.Cancel = true; return; } if(cancellationTokenSource != null) { cancellationTokenSource.Cancel(); e.Cancel = true; } return; } if(wasModified) { apiFilter.Clear(); // Add documented namespace filters this.AddNamespaceFilter(tvApiList.Nodes[0]); // Add filters for inherited types this.AddNamespaceFilter(tvApiList.Nodes[1]); } if(tempProject != null) { try { // Delete the temporary project's working files if(!String.IsNullOrEmpty(tempProject.OutputPath) && Directory.Exists(tempProject.OutputPath)) Directory.Delete(tempProject.OutputPath, true); } catch { // Eat the exception. We'll ignore it if the temporary files cannot be deleted. } tempProject.Dispose(); tempProject = null; } }
/// <inheritdoc /> public ConvertFromMSExampleGui(string oldProjectFile, SandcastleProject newProject) : base(oldProjectFile, newProject) { }
//===================================================================== /// <summary> /// Call this method to perform the build on the project. /// </summary> public void Build() { Project msBuildProject = null; ProjectItem projectItem; string resolvedPath, helpFile, languageFile, scriptFile, hintPath, message = null; SandcastleProject originalProject = null; System.Diagnostics.Debug.WriteLine("Build process starting\r\n"); try { taskRunner = new TaskRunner(this); // 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.MSBuildProject); } 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; // The version of MSBuild to use is based on the tools version set in the project 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"); // 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); } 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)); this.ReportProgress(" {0}", ComponentUtilities.ComponentsFolder); this.ReportProgress(" {0}", ComponentUtilities.ToolsFolder); // Get the framework reflection data settings to use for the build reflectionDataDictionary = new ReflectionDataSetDictionary(new[] { project.ComponentPath, Path.GetDirectoryName(project.Filename) }); frameworkReflectionData = reflectionDataDictionary.CoreFrameworkByTitle(project.FrameworkVersion, true); if(frameworkReflectionData == 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)); this.ReportProgress("Framework reflection data location: {0}", this.FrameworkReflectionDataFolder); if(!Directory.EnumerateFiles(this.FrameworkReflectionDataFolder, "*.xml").Any()) throw new BuilderException("BE0032", "Reflection data files for the selected framework " + "do not exist yet (" + frameworkReflectionData.Title + "). See help file for " + "details about this error number."); // Warn if a different framework is being used for the build if(frameworkReflectionData.Title != project.FrameworkVersion) this.ReportWarning("BE0072", "Project framework version '{0}' not found. It has been " + "redirected and will use '{1}' instead.", project.FrameworkVersion, frameworkReflectionData.Title); // Get the composition container used to find build components in the rest of the build process componentContainer = ComponentUtilities.CreateComponentContainer(new[] { project.ComponentPath, Path.GetDirectoryName(project.Filename) }, this.CancellationToken); 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)); // Create the substitution tag replacement handler now as we have everything it needs substitutionTags = new SubstitutionTagReplacement(this); // 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); } } // 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."); // 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, ".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; } if((project.HelpFileFormat & (HelpFileFormats.Website | HelpFileFormats.Markdown)) != 0) { this.ReportProgress("-------------------------------"); this.ReportProgress("Clearing any prior web/markdown output..."); // Purge all files and folders from the output path except for the working folder and the // build log. 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 in case it wasn't removed above 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); } } Directory.CreateDirectory(workingFolder); // 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); substitutionTags.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)) { substitutionTags.TransformTemplate("MRefBuilder.config", templateFolder, workingFolder); scriptFile = substitutionTags.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(BuildItemMetadata.HintPath)) { hintPath = projectItem.GetMetadataValue(BuildItemMetadata.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(BuildItemMetadata.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)) taskRunner.Run32BitProject("GenerateRefInfo.proj", false); else taskRunner.RunProject("GenerateRefInfo.proj", false); 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 = substitutionTags.TransformTemplate("TransformManifest.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("TransformManifest.proj", false); // 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"; // If there is nothing to document, stop the build if(!ComponentUtilities.XmlStreamAxis(reflectionFile, "api").Any()) 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)) { substitutionTags.TransformTemplate("GenerateInheritedDocs.config", templateFolder, workingFolder); scriptFile = substitutionTags.TransformTemplate("GenerateInheritedDocs.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("GenerateInheritedDocs.proj", true); 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.EnsureOutputFoldersExist(null); // 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(this.ConceptualContent.ContentLayoutFiles.Count != 0 || this.ConceptualContent.TokenFiles.Count != 0) { this.ReportProgress(BuildStep.CopyConceptualContent, "Copying conceptual content..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.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); this.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 = substitutionTags.TransformTemplate("GenerateIntermediateTOC.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("GenerateIntermediateTOC.proj", false); // 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); } // 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 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 frameworkReflectionData.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); substitutionTags.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(this.ConceptualContent.ContentLayoutFiles.Count != 0) { this.ReportProgress(" conceptual.config"); resolvedPath = presentationStyle.ResolvePath(presentationStyle.ConceptualBuildConfiguration); substitutionTags.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(); commentsFiles = null; // Build the conceptual help topics if(this.ConceptualContent.ContentLayoutFiles.Count != 0) { this.ReportProgress(BuildStep.BuildConceptualTopics, "Building conceptual help topics..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("BuildConceptualTopics.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildConceptualTopics.proj", false); this.ExecutePlugIns(ExecutionBehaviors.After); } } // Build the reference help topics this.ReportProgress(BuildStep.BuildReferenceTopics, "Building reference help topics..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("BuildReferenceTopics.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildReferenceTopics.proj", false); 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.HtmlHelp1 | HelpFileFormats.Website)) != 0) { this.ReportProgress(BuildStep.ExtractingHtmlInfo, "Extracting HTML info for HTML Help 1 and/or website..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("ExtractHtmlInfo.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("ExtractHtmlInfo.proj", true); 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); substitutionTags.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 = substitutionTags.TransformTemplate("Build1xHelpFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("Build1xHelpFile.proj", true); 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)) { substitutionTags.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 substitutionTags.TransformTemplate("InstallMSHC.bat", templateFolder, workingFolder); File.Move(workingFolder + "InstallMSHC.bat", workingFolder + "Install_" + this.ResolvedHtmlHelpName + ".bat"); substitutionTags.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 = substitutionTags.TransformTemplate("BuildHelpViewerFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildHelpViewerFile.proj", true); 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 = substitutionTags.TransformTemplate("BuildOpenXmlFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildOpenXmlFile.proj", true); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormats.Markdown) != 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 Markdown format, there is no project file to compile and the TOC layout is // generated by the build task. All of the necessary TOC info is stored in the intermediate // TOC file generated prior to building the topics. The build task uses it to find the // topics to finalize and generate the sidebar TOC file. this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Executing informational " + "Generate Table of Contents build step for plug-ins (not used for Markdown)"); currentFormat = HelpFileFormats.Markdown; 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 Markdown)"); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.ExecutePlugIns(ExecutionBehaviors.After); } // Generate the markdown content this.ReportProgress(BuildStep.CompilingHelpFile, "Generating markdown content..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("GenerateMarkdownContent.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("GenerateMarkdownContent.proj", true); 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: 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(OperationCanceledException ) { buildCancelling = true; this.ReportError(BuildStep.Canceled, "BE0064", "BUILD CANCELLED BY USER"); System.Diagnostics.Debug.WriteLine("Build process aborted\r\n"); } catch(Exception ex) { System.Diagnostics.Debug.WriteLine(ex); var agEx = ex as AggregateException; if(agEx != null) foreach(var inEx in agEx.InnerExceptions) { if(message != null) message += "\r\n\r\n"; message += inEx.Message + "\r\n" + inEx.StackTrace; } var bex = ex as BuilderException; do { if(message != null) message += "\r\n\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 { 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 { 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(this.CurrentBuildStep == BuildStep.Completed && !project.KeepLogFile) File.Delete(this.LogFilename); } } }
/// <summary> /// Shut down the build process thread and clean up on exit /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void ApiFilterEditorDlg_FormClosing(object sender, FormClosingEventArgs e) { if(buildThread != null && buildThread.IsAlive) { if(MessageBox.Show("A build is currently taking place to " + "obtain API information. Do you want to abort it and " + "close this form?", Constants.AppName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { e.Cancel = true; return; } try { this.Cursor = Cursors.WaitCursor; if(buildThread != null) buildThread.Abort(); while(buildThread != null && !buildThread.Join(1000)) Application.DoEvents(); // Give it a short wait. Sometimes we're still faster shutting down // than the thread is. Thread.Sleep(500); System.Diagnostics.Debug.WriteLine("Thread stopped"); } finally { this.Cursor = Cursors.Default; buildThread = null; buildProcess = null; } } if(wasModified) { apiFilter.Clear(); // Add documented namespace filters this.AddNamespaceFilter(tvApiList.Nodes[0]); // Add filters for inherited types this.AddNamespaceFilter(tvApiList.Nodes[1]); } if(tempProject != null) { try { // Delete the temporary project's working files if(!String.IsNullOrEmpty(tempProject.OutputPath) && Directory.Exists(tempProject.OutputPath)) Directory.Delete(tempProject.OutputPath, true); } catch { // Eat the exception. We'll ignore it if the temporary files cannot be deleted. } tempProject.Dispose(); tempProject = null; } GC.Collect(2); GC.WaitForPendingFinalizers(); GC.Collect(2); }
/// <summary> /// Constructor /// </summary> /// <param name="buildProject">The project to build</param> /// <param name="partialBuildType">The partial build type to perform</param> public BuildProcess(SandcastleProject buildProject, PartialBuildType partialBuildType) : this(buildProject) { this.PartialBuildType = partialBuildType; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="buildProject">The project to build</param> /// <overloads>There are two overloads for the constructor.</overloads> public BuildProcess(SandcastleProject buildProject) { project = buildProject; // Save a copy of the project filename. If using a temporary project, it won't match the passed // project's name. originalProjectName = buildProject.Filename; apiTocOrder = -1; apiTocParentId = rootContentContainerId = String.Empty; help1Files = new Collection<string>(); helpViewerFiles = new Collection<string>(); websiteFiles = new Collection<string>(); openXmlFiles = new Collection<string>(); markdownFiles = new Collection<string>(); helpFormatOutputFolders = new Collection<string>(); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="buildProject">The project to build</param> /// <overloads>There are two overloads for the constructor.</overloads> public BuildProcess(SandcastleProject buildProject) { project = buildProject; // Save a copy of the project filename. If using a temporary project, it won't match the passed // project's name. originalProjectName = buildProject.Filename; apiTocOrder = -1; apiTocParentId = rootContentContainerId = String.Empty; progressArgs = new BuildProgressEventArgs(); fieldMatchEval = new MatchEvaluator(OnFieldMatch); contentMatchEval = new MatchEvaluator(OnContentMatch); linkMatchEval = new MatchEvaluator(OnLinkMatch); codeBlockMatchEval = new MatchEvaluator(OnCodeBlockMatch); excludeElementEval = new MatchEvaluator(OnExcludeElement); help1Files = new Collection<string>(); help2Files = new Collection<string>(); helpViewerFiles = new Collection<string>(); websiteFiles = new Collection<string>(); openXmlFiles = new Collection<string>(); helpFormatOutputFolders = new Collection<string>(); }
//===================================================================== /// <summary> /// This is used to start the background build process from which /// we will get the information to load the tree view. /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void ApiFilterEditorDlg_Load(object sender, EventArgs e) { string tempPath; tvApiList.Enabled = splitContainer.Panel2.Enabled = btnReset.Enabled = false; try { // Clone the project for the build and adjust its properties // for our needs. tempProject = new SandcastleProject(apiFilter.Project, true); // The temporary project resides in the same folder as the // current project (by filename only, it isn't saved) to // maintain relative paths. However, build output is stored // in a temporary folder and it keeps the intermediate files. tempProject.CleanIntermediates = false; tempPath = Path.GetTempFileName(); File.Delete(tempPath); tempPath = Path.Combine(Path.GetDirectoryName(tempPath), "SHFBPartialBuild"); if(!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); tempProject.OutputPath = tempPath; buildProcess = new BuildProcess(tempProject, true); // We must suppress the current API filter for this build buildProcess.SuppressApiFilter = true; buildProcess.BuildStepChanged += new EventHandler<BuildProgressEventArgs>( buildProcess_BuildStepChanged); buildProcess.BuildProgress += new EventHandler<BuildProgressEventArgs>( buildProcess_BuildProgress); buildThread = new Thread(new ThreadStart(buildProcess.Build)); buildThread.Name = "API fitler partial build thread"; buildThread.IsBackground = true; buildThread.Start(); } catch(Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); MessageBox.Show("Unable to build project to obtain " + "API information. Error: " + ex.Message, Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); } }
//===================================================================== /// <summary> /// This is used to start the background build process from which we will get the information to load the /// tree view. /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private async void ApiFilterEditorDlg_Load(object sender, EventArgs e) { string tempPath; tvApiList.Enabled = splitContainer.Panel2.Enabled = btnReset.Enabled = false; try { // Clone the project for the build and adjust its properties for our needs tempProject = new SandcastleProject(apiFilter.Project.MSBuildProject); // Build output is stored in a temporary folder and it keeps the intermediate files tempProject.CleanIntermediates = false; tempPath = Path.GetTempFileName(); File.Delete(tempPath); tempPath = Path.Combine(Path.GetDirectoryName(tempPath), "SHFBPartialBuild"); if(!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); tempProject.OutputPath = tempPath; cancellationTokenSource = new CancellationTokenSource(); buildProcess = new BuildProcess(tempProject, PartialBuildType.GenerateReflectionInfo) { ProgressReportProvider = new Progress<BuildProgressEventArgs>(buildProcess_ReportProgress), CancellationToken = cancellationTokenSource.Token, SuppressApiFilter = true // We must suppress the current API filter for this build }; await Task.Run(() => buildProcess.Build(), cancellationTokenSource.Token); if(!cancellationTokenSource.IsCancellationRequested) { // Restore the current project's base path Directory.SetCurrentDirectory(Path.GetDirectoryName(apiFilter.Project.Filename)); // If successful, load the namespace nodes, and enable the UI if(buildProcess.CurrentBuildStep == BuildStep.Completed) { reflectionFile = buildProcess.ReflectionInfoFilename; // Convert the build API filter to a dictionary to make it easier to find entries buildFilterEntries = new Dictionary<string, ApiFilter>(); this.ConvertApiFilter(buildProcess.CurrentProject.ApiFilter); this.LoadNamespaces(); tvApiList.Enabled = splitContainer.Panel2.Enabled = btnReset.Enabled = true; } else MessageBox.Show("Unable to build project to obtain API information. Please perform a " + "normal build to identify and correct the problem.", Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); pbWait.Visible = lblLoading.Visible = false; } else { this.DialogResult = DialogResult.Cancel; this.Close(); } buildProcess = null; } catch(Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); MessageBox.Show("Unable to build project to obtain API information. Error: " + ex.Message, Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { if(cancellationTokenSource != null) { cancellationTokenSource.Dispose(); cancellationTokenSource = null; } } }
/// <summary> /// Convert a conceptual content topic and all of its children /// </summary> /// <param name="xr">The XML reader containing the topics</param> /// <param name="xw">The XML writer to which they are written</param> /// <param name="project">The project to which the files are added</param> private void ConvertTopic(XmlReader xr, XmlWriter xw, SandcastleProject project) { FileItem newFile = null; string id, file, title, tocTitle, linkText, destFile, ext, name, value, oldFolder = converter.OldFolder, projectFolder = converter.ProjectFolder; bool visible; int revision; id = xr.GetAttribute("id"); // If not set, an ID will be created when needed if(id == null || id.Trim().Length == 0) id = new Guid(id).ToString(); file = xr.GetAttribute("file"); title = xr.GetAttribute("title"); tocTitle = xr.GetAttribute("tocTitle"); linkText = xr.GetAttribute("linkText"); if(!Int32.TryParse(xr.GetAttribute("revision"), out revision) || revision < 1) revision = 1; if(!Boolean.TryParse(xr.GetAttribute("visible"), out visible)) visible = true; // Add the topic to the content file and the project xw.WriteStartElement("Topic"); xw.WriteAttributeString("id", id); if(file != null && file.Trim().Length > 0) { file = converter.FullPath(file); // Maintain any additional path info beyond the old base // project folder. if(file.StartsWith(oldFolder, StringComparison.OrdinalIgnoreCase)) destFile = projectFolder + file.Substring(oldFolder.Length); else destFile = Path.Combine(projectFolder, Path.GetFileName(file)); ext = Path.GetExtension(destFile).ToLowerInvariant(); // Change the extension on .xml files to .aml and add the // root topic element. if(ext == ".xml") { destFile = Path.ChangeExtension(destFile, ".aml"); ConvertTopic(file, destFile, id, revision); file = destFile; } // Add meta elements for the ID and revision number if(ext == ".htm" || ext == ".html" || ext == ".topic") { ConvertTopic(file, destFile, id, revision); file = destFile; } newFile = project.AddFileToProject(file, destFile); newFile.BuildAction = BuildAction.None; } else xw.WriteAttributeString("noFile", "True"); if(title != null) xw.WriteAttributeString("title", title); if(tocTitle != null) xw.WriteAttributeString("tocTitle", tocTitle); if(linkText != null) xw.WriteAttributeString("linkText", linkText); xw.WriteAttributeString("visible", visible.ToString(CultureInfo.InvariantCulture)); // Add child elements, if any if(!xr.IsEmptyElement) while(!xr.EOF) { xr.Read(); if(xr.NodeType == XmlNodeType.EndElement && xr.Name == "topic") break; if(xr.NodeType == XmlNodeType.Element) if(xr.Name == "helpAttributes") { xw.WriteStartElement("HelpAttributes"); while(!xr.EOF && xr.NodeType != XmlNodeType.EndElement) { if(xr.NodeType == XmlNodeType.Element && xr.Name == "helpAttribute") { name = xr.GetAttribute("name"); value = xr.GetAttribute("value"); if(!String.IsNullOrEmpty(name)) { xw.WriteStartElement("HelpAttribute"); xw.WriteAttributeString("name", name); xw.WriteAttributeString("value", value); xw.WriteEndElement(); } } xr.Read(); } xw.WriteEndElement(); } else if(xr.Name == "helpKeywords") { xw.WriteStartElement("HelpKeywords"); while(!xr.EOF && xr.NodeType != XmlNodeType.EndElement) { if(xr.NodeType == XmlNodeType.Element && xr.Name == "helpKeyword") { name = xr.GetAttribute("index"); value = xr.GetAttribute("term"); if(!String.IsNullOrEmpty(name)) { xw.WriteStartElement("HelpKeyword"); xw.WriteAttributeString("index", name); xw.WriteAttributeString("term", value); xw.WriteEndElement(); } } xr.Read(); } xw.WriteEndElement(); } else if(xr.Name == "topic") this.ConvertTopic(xr, xw, project); } xw.WriteEndElement(); }
//===================================================================== /// <summary> /// This is used to execute the task and perform the build /// </summary> /// <returns>True on success or false on failure.</returns> public override bool Execute() { Project msBuildProject = null; string line; try { if(!alwaysLoadProject) { // Use the current project if possible. This is preferable // as we can make use of command line property overrides // and any other user-defined properties. msBuildProject = this.GetCurrentProject(); if(msBuildProject == null) { // We can't use a prior MSBuild version if(isPriorMSBuildVersion) { Log.LogError(null, "BHT0004", "BHT0004", "SHFB", 0, 0, 0, 0, "An older MSBuild version is " + "being used. Unable to build help project."); return false; } Log.LogWarning(null, "BHT0001", "BHT0001", "SHFB", 0, 0, 0, 0, "Unable to get executing project: " + "Unable to obtain internal reference. The " + "specified project will be loaded but command " + "line property overrides will be ignored."); } } } catch(Exception ex) { // Ignore exceptions but issue a warning and fall back to using // the passed project filename instead. Log.LogWarning(null, "BHT0001", "BHT0001", "SHFB", 0, 0, 0, 0, "Unable to get executing project: {0}. The specified " + "project will be loaded but command line property " + "overrides will be ignored.", ex.Message); } if(msBuildProject == null) { // Create the project and set the configuration and platform // options. msBuildProject = new Project(Engine.GlobalEngine); msBuildProject.GlobalProperties.SetProperty( ProjectElement.Configuration, configuration); msBuildProject.GlobalProperties.SetProperty( ProjectElement.Platform, platform); // Override the OutDir property if defined for Team Build if(!String.IsNullOrEmpty(outDir)) msBuildProject.GlobalProperties.SetProperty( ProjectElement.OutDir, outDir); if(!File.Exists(projectFile)) throw new BuilderException("BHT0003", "The specified " + "project file does not exist: " + projectFile); msBuildProject.Load(projectFile); } // Load the MSBuild project and associate it with a SHFB // project instance. sandcastleProject = new SandcastleProject(msBuildProject, true); try { buildProcess = new BuildProcess(sandcastleProject); buildProcess.BuildStepChanged += new EventHandler<BuildProgressEventArgs>( buildProcess_BuildStepChanged); buildProcess.BuildProgress += new EventHandler<BuildProgressEventArgs>( buildProcess_BuildProgress); // Since this is an MSBuild task, we'll run it directly rather // than in a background thread. Log.LogMessage("Building {0}", msBuildProject.FullFileName); buildProcess.Build(); } catch(Exception ex) { Log.LogError(null, "BHT0002", "BHT0002", "SHFB", 0, 0, 0, 0, "Unable to build project '{0}': {1}", msBuildProject.FullFileName, ex); } if(dumpLogOnFailure && lastBuildStep == BuildStep.Failed) using(StreamReader sr = new StreamReader(buildProcess.LogFilename)) { Log.LogMessage(MessageImportance.High, "Log Content:"); do { line = sr.ReadLine(); // Don't output the XML elements, just the text if(line != null && (line.Trim().Length == 0 || line.Trim()[0] != '<')) Log.LogMessage(MessageImportance.High, line); } while(line != null); } return (lastBuildStep == BuildStep.Completed); }
//===================================================================== /// <summary> /// This is used to perform the actual conversion /// </summary> /// <returns>The new project filename on success. An exception is /// thrown if the conversion fails.</returns> public override string ConvertProject() { XPathNavigator navProject; Topic t; string lastProperty = null; try { project = base.Project; project.HelpTitle = project.HtmlHelpName = Path.GetFileNameWithoutExtension(base.OldProjectFile); // We'll process it as XML rather than an MSBuild project as // it may contain references to target files that don't exist // which would prevent it from loading. docProject = new XmlDocument(); docProject.Load(base.OldProjectFile); nsm = new XmlNamespaceManager(docProject.NameTable); nsm.AddNamespace("prj", docProject.DocumentElement.NamespaceURI); navProject = docProject.CreateNavigator(); lastProperty = "ProjectExtensions/VisualStudio"; this.ImportProjectExtensionProperties(); // Parse each build item to look for stuff we need to add // to the project. foreach(XPathNavigator buildItem in navProject.Select( "//prj:Project/prj:ItemGroup/*", nsm)) { lastProperty = String.Format(CultureInfo.InvariantCulture, "{0} ({1})", buildItem.Name, buildItem.GetAttribute("Include", String.Empty)); switch(buildItem.Name) { case "Content": case "None": this.ImportFile(buildItem); break; case "ProjectReference": project.DocumentationSources.Add(base.FullPath( buildItem.GetAttribute("Include", String.Empty)), null, null, false); break; default: // Ignore break; } } // Add project-level help attributes if any if(topicSettings.TryGetValue("*", out t)) foreach(MSHelpAttr ha in t.HelpAttributes) project.HelpAttributes.Add(ha); if(topicLayoutFilename != null) this.CreateContentLayoutFile(); else if(topicSettings.Count != 0) this.CreateDefaultContentLayoutFile(); base.CreateFolderItems(); project.SaveProject(project.Filename); } catch(Exception ex) { throw new BuilderException("CVT0005", String.Format( CultureInfo.CurrentCulture, "Error reading project " + "from '{0}' (last property = {1}):\r\n{2}", base.OldProjectFile, lastProperty, ex.Message), ex); } return project.Filename; }
/// <inheritdoc /> public ConvertFromNDoc(string oldProjectFile, SandcastleProject newProject) : base(oldProjectFile, newProject) { }
/// <summary> /// Constructor /// </summary> /// <param name="project">The project into which the converted elements will be inserted</param> /// <remarks>This constructor is used by the VSPackage to convert an old project after it creates /// a new project instance.</remarks> public NewFromOtherFormatDlg(SandcastleProject project) : this() { currentProject = project; txtNewProjectFolder.Text = project.Filename; txtNewProjectFolder.Enabled = btnSelectNewFolder.Enabled = false; }