📜 ⬆️ ⬇️

And a few words about SandCastle, TFS and magic ...

Based on the just- missed publication Sandcastle and SHFB, I decided to share my pains and sorrows, as well as the success-story while working with this product.

In the text there will be no screenshots with captions " click ADD " and descriptions of settings / plugins.
The text will describe the process of implementing a specific case: assembling SHFB documentation in TFS.

So, the current environment:


What is the problem


My first and main concern in organizing documentation was to keep the developer away from the further process of building documentation. Those. so that the conditional junior could write code, commit it to TFS, and the documentation itself was collected with a successful build of the release version.
')
So we come to the first problem. It lies just the same in this junior-developer. How to make him post comments? This will help us ...

Stylecop


Or rather StyleCop checkin policy . I had to finish it a bit to pick up the config file directly from TFS (so as not to pour out a new version to all developers every time). But in general, the principle is clear, yes? We set up the rules we need regarding documentation, turn on the policy and set up a notification for each Policy override in TFS - we cannot completely ban it (technically we can , but cases where it will really be necessary to do an override will turn into completely transcendent pain) from here over the developer’s shoulder a minute after he clicked on the " Override policy " and clearly explain what he is wrong. Conveniently. Clearly. Inspires.

So, with Chekina and formatting code sorted out. We go further.

Junior regularly whines that he is tired of writing the same thing. There are regular situations when the method has corrected the description, and in its five overloads it has been forgotten. This will help us ...

<inheritdoc />


SHFB supports the <inheritdoc /> tag. It allows you to get rid of the mass copy-paste in the attributes of the description. For the sake of his acceptable functioning, you need to dance a little with a tambourine, to guess its capabilities ( because the official documentation is quite extensive and does not go into the technical details of the implementation of this function - I had to dig in the raw materials to catch where he came from tree inherited types ).

For example, we have a class:

/// <inheritdoc /> /// <summary>   NLog.</summary> public class NLogWrapper : ILogger, IWithTags { /// <inheritdoc /> public virtual bool IsTraceEnabled { get { return InnerLogger.IsTraceEnabled; } } /// <inheritdoc /> public string Name { get; set; } /// <inheritdoc cref="IWithTags.Tags"/> public HashSet<string> Tags { get; set; } ... } 


In the resulting NLogWrapper class documentation, the descriptions of IsTraceEnabled and Name are inherited from ILogger , and the Tags from IWithTags . Conveniently. It would seem - here it is, happiness! But no.
Sadness # 1 with this inheritdoc is that it works on etheric matters through astral bodies and you can almost never be sure that any of the cases will work until you try. For example:

Etc. In general, a useful thing, but you need to think carefully before using it.

Well, we have finished with this preparation, let's get down to the main point.

Tfs build


So what do we want? And we want for our assembly, along with all the projects in it was the documentation.
To begin with, we put SHFB on the server where our build-agent is spinning. Otherwise it will not work. It uses environment variables, a bunch of local files ... In general, you have to set.

Next, open the gui SHFB, set up the project, add our .sln file as the Documentation Sources, save it. We read the instructions . Everything looks rather trivial. Create a build.proj file according to the instructions in order to fool dancing with OutputDir (I tried without it - there it begins with such paths that it’s true that it’s better to make an extra .proj wrapper):

 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <Target Name="Build"> <!-- Build source code docs --> <MSBuild Projects="My.Api.shfbproj" Properties="Configuration=Release;Platform=AnyCPU;OutDir=$(OutDir)" /> </Target> </Project> 

Run:

 SHFB : error BE0040: Project assembly does not exist 

Ummm, what? Who are you?

And this, friends, is a rake: although the sfhbproj file is in fact an msbuild-project, and even allows you to operate with .sln-files as sources, only it does not build it itself . Those. he uses this .sln file only to find a list of projects, and in them to find an OutputFolder for the specified configuration and from there take ready-made .dll / xml files .

That is because a lazy beast something. Okay, now we will teach new tricks. We climb in the file, we see there
 <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" /> 

Yeah. After a pretty quick insight, we understand that $ (SHFBROOT) is nothing but the installation folder of the SHFB binaries. There we find this file. We are looking at where we should go in ... Yeah, here it is:

  <PropertyGroup> <BuildDependsOn> PreBuildEvent; BeforeBuildHelp; CoreBuildHelp; AfterBuildHelp; PostBuildEvent </BuildDependsOn> </PropertyGroup> <Target Name="Build" DependsOnTargets="$(BuildDependsOn)" /> 

Take, for example, BeforeBuildHelp . Another piece of documentation that will help us live is here . Slightly modify our build.proj:

 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <Target Name="Build"> <!-- Build source code docs --> <MSBuild Projects="My.Api.shfbproj" Properties="Configuration=Release;Platform=AnyCPU;OutDir=$(OutDir);CustomAfterSHFBTargets=$(MSBuildThisFileDirectory)shfbcustom.targets" /> </Target> </Project> 

(added CustomAfterSHFBTargets ) and create this file shfbcustom.targets:

 <?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <Target Name="BeforeBuildHelp"> <XmlPeek Namespaces="<Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>" XmlContent="<root>$(DocumentationSources)</root>" Query="//msb:DocumentationSource[@configuration]/@sourceFile"> <Output TaskParameter="Result" ItemName="Peeked" /> </XmlPeek> <MSBuild Projects="@(Peeked)" Properties="Configuration=Doc;Platform=Any CPU;OutDir=$(OutDir)" /> </Target> </Project> 

Here is a little bit of magic. In the file My.Api.shfbproj in the <DocumentationSources> property is stored ... XML. String. Here is a tricky move. Against it, we can use just the same tricky move: our Overload target BeforeBuildHelp takes this line, feeds it to the XmlPeek task and takes out all @sourceFile from the nodes that have @configuration . It then feeds this array to MSBuild.

Yes, at the same time, we lose the Configuration | Platform settings, which could be specified in the SHFB for these sources, but I could just endure this pain: a special assembly configuration called Doc is used for documentation (as seen above in the code). This is a copy of the release, with disconnected test projects and other unnecessary things that would otherwise prevent the normal dock from generating. Those. it would be possible to make this file three times thicker, to parse its parameters for each .sln, but in our case it was not worth it.

Run again ... Wow - going!
So, i.e. we already have a project that can be configured in SHFB, including new .sln, and then just run the build in TFS and get chm + html at the output ?! Perfectly. Look ... oh, what is it? In the error log:

 SHFB: Warning GID0002: No comments found for cref 'T:System.Web.Http.Dependencies.IDependencyResolver' on member 'T:My.Api.Server.DependencyResolver' 

See the code:
  /// <summary> /// DependencyResolver  Unity /// </summary> /// <inheritdoc cref="System.Web.Http.Dependencies.IDependencyResolver" /> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "IDisposable    .")] public class DependencyResolver : DependencyScope, IDependencyResolver { /// <inheritdoc /> public DependencyResolver(IUnityContainer container) : base(container) { } /// <inheritdoc /> public IDependencyScope BeginScope() { Log.Trace("Beginning new scope"); return new DependencyScope(Container.CreateChildContainer()); } } 

It seems that everything is clean, <inheritdoc /> is, it is registered normally - it should be!

[cut]


Above cut a few hours of searching, picking in the settings, then in the source code of the SHFB itself and its pieces ... As a result, it turned out:

The source for <inheritdoc /> is taken EXCLUSIVELY the data specified in DocumentationSources . However, they must be written directly in the file.

No plugins will help. No References count. No MSBuild magic that allows modifying variables on the fly does not help either. Because in the end, the GenerateInheritedDocs.exe file is launched, which stupidly parses the .shfbproj file, extracts the contents of the node through the XPath and iterates through the files specified there. Everything has arrived. I tried, it was, to cut this obscurantism, but there the direct work with the file was inserted at every step - each component in itself climbs into it and reads what it needs - there is no talk of any general context. So I abandoned this venture.

So if you want to insert into your documentation the lines from the components that you use in the project (in this case, I wanted to have a description of the methods from System.Web.Http ), then you will have to include these components in the DocumentationSources .

Yes, you can include not the assembly itself, but only the .xml file from it. This is not much easier.
At this point, we clearly get hemorrhoids with the support of the .shfbproj file - we need to update it every time new components are used. We need to update it every time we update the nuget-package - because the path to the file changes! Horror-horror. And do not automate the same.

No, of course, you can make such a target, to sort through the contents of / packages / ** and pull out all the .xml from there ... But, no, it is impossible - each package can contain several versions for different versions of the .net runtime. So, it is necessary to go from the other end - after assembling each project, iterate through the entire contents of $ (OutDir), and from there enter all xml / dll files into ... But where?

Here you can beat a little: the inclusion of .shfbproj is supported as a Documentation Source. So you can create a minimum content file on the fly, which will only have DocumentationSources , and keep it the only inclusion in the main file ... But it smacks of something with this, I think.

To (un-) fortunately, I did all this as an elective and out of personal interest, I soon had to take on another project, and it all remained in this form - it is going to, published, but to update / maintain - pain.

What is left over?


Source: https://habr.com/ru/post/243171/


All Articles