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

Based on the just skipped publication , Sandcastle and SHFB decided to share their pains and sorrows, as well as the success story when working with this product.

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

So, the existing environment:
  • Team Foundation Server 2013
  • VisualStudio 2014

What is the problem

My first and main concern when organizing the documentation was to keep the developer as far away as possible 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 already assembled with a successful build of the release version.

So we come to the first problem. It consists precisely in this junior developer. How to get him to put comments? This will help us ...


More specifically, StyleCop checkin policy . I had to finish it a bit to take the config file directly from TFS (so as not to spill the new version to all developers every time). But in general, the principle is clear, right? We configure the rules we need regarding documentation, enable policy and set up an alert in TFS for each Policy override - we cannot completely ban it (technically we can , but the cases when you really need to make an override will turn into an absolutely unbelievable pain), but we can loom from nowhere over the developer’s shoulder a minute after he clicked “ Override policy ” and clearly explain what he’s wrong with. Conveniently. Clearly. It inspires.

So, with chekins and code formatting sorted out. We are going further.

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

SHFB supports tag . It allows you to get rid of the mass of copy-paste in the attributes of the description. For the sake of its acceptable functioning, you need to dance a bit with a tambourine, guess its capabilities ( because the official documentation is quite lengthy and does not go into the technical details of the implementation of this function - I had to delve into the raw materials to catch where he, for example, takes lists of files for generation tree of inherited types ).

For example, we have a class:

	/// Имплементация логгера для NLog.
	public class NLogWrapper : ILogger, IWithTags
		public virtual bool IsTraceEnabled 
			get { return InnerLogger.IsTraceEnabled; }
		public string Name { get; set; }
		public HashSet Tags { get; set; }

The resulting documentation class NLogWrapper description Property IsTraceEnabled and Name will be inherited from ILogger , and 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:
  • You cannot inherit descriptions of overloaded methods within the same class / interface;
  • It is not always enough to put a label on the inherited method - sometimes it is still necessary to put it on the class itself so that SHFB guesses that its ancestors should be scanned;
  • You need to manually add libraries with base classes to DocumentationSources (more on that below);
  • Required additional manipulations for IntelliSense, because "out of the box" in the resulting .xml obtained these samethat Visual Studio doesn’t eat.

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

Well, we’ve finished this with the preparation, let's get down to the main thing.

Tfs build

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

Next, open gui SHFB, set up the project, add our .sln file as Documentation Sources , save it. We read the instructions . Everything looks pretty trivial. We create the build.proj file according to the instructions in order to deceive the dances with OutputDir (without it I tried - there such hell with paths begins, which is true - it is better to make an extra .proj wrapper):

We launch:

SHFB : error BE0040: Project assembly does not exist

Ummm, what? Who are you?

And this, friends, is a rake: the sfhbproj file, although it is essentially an msbuild project, and even allows you to operate with .sln files as sources, it just doesn’t do the assembly itself . Those. He uses this .sln file only in order to find a list of projects, and in them to find the OutputFolder for the specified configuration and from there already take ready .dll / xml-files .

That’s a lazy cattle. Okay, now we’ll teach you new tricks. We climb into the file, see there

Yeah. After a fairly quick flash of light, we realize that $ (SHFBROOT) is nothing more than the installation folder for the binaries of SHFB itself. There we find this file. We look, where we could wedge ... Aha, here it is:


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

(added CustomAfterSHFBTargets ) and create the following shfbcustom.targets file:

There is a bit of magic here. In the file My.Api.shfbproj in the propertystored ... XML. String. Here is such a tricky move. On the contrary, we can only use the same tricky move: our overloading the BeforeBuildHelp target takes this line, feeds it to the XmlPeek task and takes all @sourceFile from the nodes that have @configuration from there . Then feeds this array to MSBuild task.

Yes, at the same time, we lose the project-specific Configuration | Platform settings that could be specified in SHFB for these sources, but I could survive this pain simply: for documentation, we use a special assembly configuration called Doc (as seen above in the code). This is a copy of the release, with disabled test projects and other unnecessary things that would otherwise interfere with the generation of a normal dock. 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.

We start again ... Wow - going!
So, i.e. we already have a project that can be configured in SHFB, including the new .sln, and then just run the build in TFS and get chm + html output ?! Perfectly. We 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'

We look at the code:
	/// DependencyResolver для Unity
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "IDisposable реализован в базовом классе.")]
	public class DependencyResolver : DependencyScope, IDependencyResolver
		public DependencyResolver(IUnityContainer container)
			: base(container)
		public IDependencyScope BeginScope()
			Log.Trace("Beginning new scope");
			return new DependencyScope(Container.CreateChildContainer());

Everything seems to be clean is, it is registered normally - should be!

[cut out]

Several hours of searches were cut above, picking in the settings, then in the source code of the SHFB itself and its pieces ... As a result, it turned out:

As a source forThe data specified in DocumentationSources is taken ONLY . At the same time, they must be registered directly in the file.

No plugins will help. No References are counted. No MSBuild magic that allows modifying variables on the fly will 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 XPath and iterates over the files specified there. That's it, come. I tried, it was, to cut this obscurantism, but there, at every step, direct work with the file is inserted - each component by itself crawls into it and reads what it needs - there is no talk of any general context. So I abandoned this venture.

So if you want the lines from the components that you use in the project to be inserted into your documentation (in this case, I would like there to be a description of the methods fromSystem.Web.Http ), you will have to include these components in 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 support for the .shfbproj file - you 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 that iterates over the contents of / packages / **and pulled out everything .xml from there ... And, no, it’s impossible - each package can contain several versions for different versions of .net runtime. So, you need to go from the other end - after building each project, iterate over the entire contents of $ (OutDir), and enter all the xml / dll files from there into ... But where?

You can beat a little here: the inclusion of .shfbproj as a Documentation Source is supported. So you can create a file of minimal content on the fly, in which there will be only DocumentationSources , and keep it the only inclusion in the main file ... But it smells somewhat of this, it seems to me.

Fortunately, I did all this as an optional and out of personal interest, soon I had to start another project, and this all remained in this form - it is going to be published, but updating / maintaining is a pain.

What is left over?

  • The Build project button (or the Continuos Integration rule itself) collects and publishes the documentation in .chm and html for the project. It's good;
  • Along the way, they made a rule to control negligent juniors so that they quickly come to enlightenment. It's also good;
  • To support and develop it will be someone else. Just great.

Also popular now: