// NAnt - A .NET build tool // Copyright (C) 2001-2002 Gerry Shaw // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Gerry Shaw (gerry_shaw@yahoo.com) // Ian MacLean (ian_maclean@another.com) // Giuseppe Greco (giuseppe.greco@agamura.com) using System; using System.Collections; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Text; using System.Xml; using NDoc.Core; using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Types; using NAnt.Core.Util; using NAnt.DotNet.Types; namespace NAnt.DotNet.Tasks { /// /// Runs NDoc V1.3.1 to create documentation. /// /// /// /// See the NDoc home page for more /// information. /// /// /// By default, only the NDoc MSDN documenter ships as part of the NAnt /// distribution. To make another NDoc documenter from the NDoc V1.3.1 /// distribution available to the , copy the /// documenter assembly (and possible dependencies) to the "lib" /// directory corresponding with the CLR you're running NAnt on /// (eg. <nant root>/bin/lib/net/1.1). /// /// /// /// /// Document two assemblies using the MSDN documenter. The namespaces are /// documented in NamespaceSummary.xml. /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// ]]> /// /// Content of NamespaceSummary.xml : /// /// /// /// The Foo.Bar namespace reinvents the wheel. /// /// /// The Foo.Bar.Tests namespace ensures that the Foo.Bar namespace reinvents the wheel correctly. /// /// /// ]]> /// /// [TaskName("ndoc")] public class NDocTask : Task { #region Private Instance Fields private XmlNodeList _docNodes; private AssemblyFileSet _assemblies = new AssemblyFileSet(); private FileSet _summaries = new FileSet(); private RawXml _documenters; private DirSet _referencePaths = new DirSet(); #endregion Private Instance Fields #region Public Instance Properties /// /// The set of assemblies to document. /// [BuildElement("assemblies", Required=true)] public AssemblyFileSet Assemblies { get { return _assemblies; } set { _assemblies = value; } } /// /// The set of namespace summary files. /// [BuildElement("summaries")] public FileSet Summaries { get { return _summaries; } set { _summaries = value; } } /// /// Specifies the formats in which the documentation should be generated. /// [BuildElement("documenters", Required=true)] public RawXml Documenters { get { return _documenters; } set { _documenters = value; } } /// /// Collection of additional directories to search for referenced /// assemblies. /// [BuildElement("referencepaths")] public DirSet ReferencePaths { get { return _referencePaths; } set { _referencePaths = value; } } #endregion Public Instance Properties #region Override implementation of Task /// /// Initializes the taks and verifies the parameters. /// /// containing the XML fragment used to define this task instance. protected override void InitializeTask(XmlNode taskNode) { // expand and store clone of the xml node _docNodes = Documenters.Xml.Clone().SelectNodes("nant:documenter", NamespaceManager); ExpandPropertiesInNodes(_docNodes); } /// /// Generates an NDoc project and builds the documentation. /// protected override void ExecuteTask() { // ensure base directory is set, even if fileset was not initialized // from XML if (Assemblies.BaseDirectory == null) { Assemblies.BaseDirectory = new DirectoryInfo(Project.BaseDirectory); } if (Summaries.BaseDirectory == null) { Summaries.BaseDirectory = new DirectoryInfo(Project.BaseDirectory); } if (ReferencePaths.BaseDirectory == null) { ReferencePaths.BaseDirectory = new DirectoryInfo(Project.BaseDirectory); } // Make sure there is at least one included assembly. This can't // be done in the InitializeTask() method because the files might // not have been built at startup time. if (Assemblies.FileNames.Count == 0) { throw new BuildException(ResourceUtils.GetString("NA2020"), Location); } // create NDoc Project NDoc.Core.Project project = null; try { project = new NDoc.Core.Project(); } catch (Exception ex) { throw new BuildException(ResourceUtils.GetString("NA2021"), Location, ex); } // set-up probe path, meaning list of directories where NDoc searches // for documenters // by default, NDoc scans the startup path of the app, so we do not // need to add this explicitly string privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath; if (privateBinPath != null) { // have NDoc also probe for documenters in the privatebinpath foreach (string relativePath in privateBinPath.Split(Path.PathSeparator)) { project.AppendProbePath(Path.Combine( AppDomain.CurrentDomain.BaseDirectory, relativePath)); } } // check for valid documenters (any other validation can be done by NDoc itself at project load time) foreach (XmlNode node in _docNodes) { //skip non-nant namespace elements and special elements like comments, pis, text, etc. if (!(node.NodeType == XmlNodeType.Element) || !node.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) { continue; } string documenterName = node.Attributes["name"].Value; CheckAndGetDocumenter(project, documenterName); } // write documenter project settings to temp file string projectFileName = Path.GetTempFileName(); Log(Level.Verbose, ResourceUtils.GetString("String_WritingProjectSettings"), projectFileName); XmlTextWriter writer = new XmlTextWriter(projectFileName, Encoding.UTF8); writer.Formatting = Formatting.Indented; writer.WriteStartDocument(); writer.WriteStartElement("project"); // write assemblies section writer.WriteStartElement("assemblies"); foreach (string assemblyPath in Assemblies.FileNames) { string docPath = Path.ChangeExtension(assemblyPath, ".xml"); writer.WriteStartElement("assembly"); writer.WriteAttributeString("location", assemblyPath); if (File.Exists(docPath)) { writer.WriteAttributeString("documentation", docPath); } writer.WriteEndElement(); } writer.WriteEndElement(); // write summaries section StringBuilder sb = new StringBuilder(); foreach (string summaryPath in Summaries.FileNames) { // write out the namespace summary nodes try { XmlTextReader tr = new XmlTextReader(summaryPath); tr.MoveToContent(); // skip XmlDeclaration and Processing Instructions sb.Append(tr.ReadOuterXml()); tr.Close(); } catch (IOException ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA2022"), summaryPath), Location, ex); } } writer.WriteRaw(sb.ToString()); // write out the documenters section writer.WriteStartElement("documenters"); foreach (XmlNode node in _docNodes) { //skip non-nant namespace elements and special elements like comments, pis, text, etc. if (!(node.NodeType == XmlNodeType.Element) || !node.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) { continue; } writer.WriteRaw(node.OuterXml); } writer.WriteEndElement(); // end project element writer.WriteEndElement(); writer.Close(); try { // read NDoc project file Log(Level.Verbose, ResourceUtils.GetString("String_NDocProjectFile"), Path.GetFullPath(projectFileName)); project.Read(projectFileName); // add additional directories to search for referenced assemblies if (ReferencePaths.DirectoryNames.Count > 0) { foreach (string directory in ReferencePaths.DirectoryNames) { project.ReferencePaths.Add(new ReferencePath(directory)); } } foreach (XmlNode node in _docNodes) { //skip non-nant namespace elements and special elements like comments, pis, text, etc. if (!(node.NodeType == XmlNodeType.Element) || !node.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) { continue; } string documenterName = node.Attributes["name"].Value; IDocumenter documenter = CheckAndGetDocumenter(project, documenterName); // hook up events for feedback during the build documenter.DocBuildingStep += new DocBuildingEventHandler(OnDocBuildingStep); documenter.DocBuildingProgress += new DocBuildingEventHandler(OnDocBuildingProgress); // build documentation documenter.Build(project); } } catch (Exception ex) { throw new BuildException(ResourceUtils.GetString("NA2023"), Location, ex); } } #endregion Override implementation of Task #region Private Instance Methods /// /// Represents the method that will be called to update the overall /// percent complete value and the current step name. /// /// The source of the event. /// A that contains the event data. private void OnDocBuildingStep(object sender, ProgressArgs e) { Log(Level.Info, e.Status); } /// /// Represents the method that will be called to update the current /// step's precent complete value. /// /// The source of the event. /// A that contains the event data. private void OnDocBuildingProgress(object sender, ProgressArgs e) { Log(Level.Verbose, e.Progress + ResourceUtils.GetString("String_PercentageComplete")); } /// /// Returns the documenter for the given project. /// /// /// Documenter is not found. /// /// /// is . /// private IDocumenter CheckAndGetDocumenter(NDoc.Core.Project project, string documenterName){ IDocumenter documenter = null; if (project == null) { throw new ArgumentNullException("project"); } StringCollection documenters = new StringCollection(); foreach (IDocumenter d in project.Documenters) { documenters.Add(d.Name); // ignore case when comparing documenter names if (string.Compare(d.Name, documenterName, true, CultureInfo.InvariantCulture) == 0) { documenter = (IDocumenter) d; break; } } // throw an exception if the documenter could not be found. if (documenter == null) { if (documenters.Count == 0) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA2024"), documenterName), Location); } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA2025"), documenterName, StringUtils.Join(", ", documenters)), Location); } } return documenter; } /// /// Performs macro expansion for the given nodes. /// /// for which expansion should be performed. private void ExpandPropertiesInNodes(XmlNodeList nodes) { foreach (XmlNode node in nodes) { // do not process comment nodes, or entities and other internal element types. if (node.NodeType == XmlNodeType.Element) { ExpandPropertiesInNodes(node.ChildNodes); foreach (XmlAttribute attr in node.Attributes) { // use "this" keyword as workaround for Mono bug #71992 attr.Value = this.Project.ExpandProperties(attr.Value, Location); } // convert output directory to full path relative to project base directory XmlNode outputDirProperty = (XmlNode) node.SelectSingleNode("property[@name='OutputDirectory']"); if (outputDirProperty != null) { XmlAttribute valueAttribute = (XmlAttribute) outputDirProperty.Attributes.GetNamedItem("value"); if (valueAttribute != null) { // use "this" keyword as workaround for Mono bug #71992 valueAttribute.Value = this.Project.GetFullPath(valueAttribute.Value); } } } } } #endregion Private Instance Methods } }