Skip to content
Steve Gilham edited this page Jan 9, 2022 · 4 revisions

Welcome to the altcode.dixon wiki!

Simple HOW-TO guide

In a set-up stage that doesn't need to get run often (except every time on a clean CI VM), copy FxCop from under Visual Studio to some (.gitignored) location within your project. Then copy the appropriate parts from the Dixon NuGet package into the FxCop copy.

Then when running FxCop, use the copy as your executable.

Fake build example

Set-up stage

I've used a dummy .csproj to install locally tooling packages that aren't dotnet tool items (things like unit test console runners for .net Framework, or PowerShell modules such as Pester)

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <None Remove="*.*" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="AltCode.Dixon" version="2022.1.8.13182" />
    <!-- More packages go here as required -->
  </ItemGroup>
</Project>

You need this helper package

nuget BlackFox.VsWhere >= 1.0.0

in your paket directives, and FxCop under a VS2022 install like

let fxcop =
    if Environment.isWindows then
        BlackFox.VsWhere.VsInstances.getAll ()
        |> Seq.filter (fun i -> System.Version(i.InstallationVersion).Major = 17)
        |> Seq.map
            (fun i ->
                i.InstallationPath
                @@ "Team Tools/Static Analysis Tools/FxCop")
        |> Seq.filter Directory.Exists
        |> Seq.tryHead
    else
        None

Install Dixon (and any other tools) from the dummy project like (replacing {dummy} with whatever you called it

DotNet.restore (fun o ->
  { o with
      Packages = [ "./packages" ]
      Common = dotnetOptions o.Common }) "./Build/{dummy}.csproj"

and locate these items like

let toolPackages =
  let xml =
    "./Build/{dummy}.csproj"
    |> Path.getFullName
    |> XDocument.Load
  xml.Descendants(XName.Get("PackageReference"))
  |> Seq.filter (fun x -> x.Attribute(XName.Get("Include")) |> isNull |> not)
  |> Seq.map
       (fun x ->
       (x.Attribute(XName.Get("Include")).Value, x.Attribute(XName.Get("version")).Value))
  |> Map.ofSeq

let packageVersion (p: string) = p.ToLowerInvariant() + "/" + (toolPackages.Item p)

for example

let dixon =
  ("./packages/" + (packageVersion "AltCode.Dixon") + "/tools")
  |> Path.getFullName

then the local merging can be done like

  fxcop 
  |> Option.iter (fun fx -> Directory.ensure "./packages/fxcop/"
                            let target = Path.getFullName "./packages/fxcop/"
                            let prefix = fx.Length

                            let check t pf (f : string) =
                              let destination = t @@ (f.Substring pf)
                              destination |> File.Exists |> not &&
                              // This ruleset does not play well with `dotnet` libraries
                              destination |> Path.GetFileName <> "SecurityTransparencyRules.dll"

                            Shell.copyDir target fx (check target prefix)

                            Shell.copyDir target dixon (fun _ -> true)
                
                            let config = XDocument.Load "./packages/fxcop/FxCopCmd.exe.config"
                            // Maybe process here...
                            config.Save "./packages/fxcop/DixonCmd.exe.config"))
    )

When calling FxCop

Just locate the executable(s) as

let (fxcop, dixon) =
    if Environment.isWindows then
        let expect =
            "./packages/fxcop/FxCopCmd.exe"
            |> Path.getFullName

        if File.Exists expect then
            (Some expect, Some ("./packages/fxcop/DixonCmd.exe" |> Path.getFullName))
        else
            (None, None)
    else
        (None, None)

and later code to the effect of

if fxcop |> Option.isSome
then FxCop.run
       { FxCop.Params.Create() with
           ToolPath = Option.get fxcop
           ...

for framework-oriented assemblies, or

let nugetCache =
    Path.Combine(Environment.GetFolderPath Environment.SpecialFolder.UserProfile, ".nuget/packages")

if dixon |> Option.isSome
then FxCop.run
       { FxCop.Params.Create() with
           ToolPath = Option.get dixon
           Platform = @"C:\Program Files\dotnet\sdk\{version}\ref" // choose your SDK version
           DependencyDirectories = [ // add the obvious dependencies -- the error messages will say which ones
                                     nugetCache @@ "{package1}/{version2}/lib/netstandard2.0"
                                     nugetCache @@ "{package2}/{version2}/lib/netstandard2.0"
                                     ...
           ...

for netstandard2.0 only.

If things go wrong

If you're just using the rules assembly with FxCop v17 (vs2022), then there may be a fixable bug.

For the DixonCmd tool for probing netstandard2.0 -- this is a grand lash-up and a hack, and is held together by hope and desperation. There is of course no warranty, nor likelihood of significant bug-fixes.

In the easy cases, the FxCop report file should give some hints as to what dependencies are missing.

Occasionally it may help to create a synthetic platform directory with the netstandard.dll from the SDK ref directory and the mscorlib.dll from the .net Framework, especially if one or the other assembly seems implicated. Occasionally it may help to ensure that debug symbols are not embedded in the assembly under inspection -- consider this if all else fails and the stack trace mentions reading debug information. Occasionally, it may help to build an assembly using netstandard2.0 dependencies under net472 and still use Dixon -- this may help with particularly opaque stack traces, by moving things onto more supported ground.

Alas, in some cases, especially ones involving SecurityPermission attributes there seems to be no good resolution, and at times, not even good error messages -- just E_FAIL status codes from the FxCop assembly reader that may reveal the underlying cause if you use some or all of the "occasionally" tactics mentioned above.

Clone this wiki locally