Skip to content

Instantly share code, notes, and snippets.

@swlaschin
Last active March 4, 2021 19:24
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save swlaschin/5742974 to your computer and use it in GitHub Desktop.
Save swlaschin/5742974 to your computer and use it in GitHub Desktop.
This script analyzes the dependencies between top level types in a .NET Assembly. It is then used to compare the dependency relationships in some F# projects with those in some C# projects.
(*
This script analyzes the dependencies between top level types in a .NET Assembly.
It is then used to compare the dependency relationships in some F# projects with those in some C# projects.
Note that no attempt has been made to optimize the code yet!
REQUIRES:
* Mono.Cecil for code analysis
From http://www.mono-project.com/Cecil#Download
or via NuGet
Install-Package Mono.Cecil -Version 0.9.5.4
* QuickGraph for graph algorithm
or via NuGet
Install-Package QuickGraph -Version 3.6.61119.7
* GraphViz for graph rendering.
From http://www.graphviz.org/Download.php
Alternatives are: GLEE (http://research.microsoft.com/en-us/downloads/f1303e46-965f-401a-87c3-34e1331d32c5/default.aspx)
or Canviz (https://code.google.com/p/canviz/) to render DOT files with JS in the browser.
USING NUGET
NuGet requires a project, so I just create an empty project and solution and add this script to it.
Once the project exists, you can run the NuGet commands from the "Package Manager Console" .
*)
// make Visual Studio use the script directory
System.IO.Directory.SetCurrentDirectory(__SOURCE_DIRECTORY__)
#r @"packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll"
#r @"packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Rocks.dll"
#r @"packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.dll"
open System
open System.IO
// ================================
// The domain types - note there are no dependencies on Cecil
// ================================
/// Distinguish a Type name from normal strings.
/// Use a "name" rather than a Cecil.TypeDefinition/TypeReference to satisfy the comparison constraint of sets
type TypeName = TypeName of string
/// Distinguish top level types from normal types
type TltName = TltName of string
/// a dependency to another type can be internal (same assembly) or external (different assembly)
type TypeDependency =
| Internal of TypeName
| External of TypeName
/// a type with its dependencies -- references exposed publically and references used in the implementation
type TypeWithDependencies =
{ typeDef: TypeName; isAuthored: bool; publicDeps: TypeDependency Set; implDeps: TypeDependency Set}
/// a top level class/module along with all the types it contains
type TopLevelType =
{ name: TltName; types: TypeWithDependencies Set}
/// a top level class/module along with the top level types it depends on
type DependencySet =
{ dependent: TltName; dependencies: TltName Set}
// ================================
// Module for extracting types from an assembly (using Cecil)
// ================================
module AssemblyTypes =
open Mono.Cecil
/// Extract the full name from the TypeDefinition or TypeReference
let inline typeName td = TypeName ( ^a : (member FullName : string) td)
/// A type is "core" if it is in the Microsoft.FSharp namespace
/// This stops projects with Fsharp.Core built in from double counting
let isCoreName (s:string) =
s.StartsWith("Microsoft.FSharp")
// used to filter out types that should not be included
let isNotCoreType excludeCore (td:TypeDefinition) =
not (isCoreName td.FullName && excludeCore)
/// get a named attribute of the specified type, or None
let getCustomAttribute (td:TypeDefinition) attrName =
td.CustomAttributes
|> Seq.tryFind (fun a -> a.AttributeType.FullName = attrName )
/// get a named attribute constructor arg of the specified type, or None
let getCustomAttributeArg (td:TypeDefinition) attrName argName =
getCustomAttribute td attrName |> Option.bind (fun attr ->
attr.ConstructorArguments |> Seq.tryFind (fun arg -> arg.Type.FullName = argName )
)
// is the type a F# sum type?
let isFsSumType (td:TypeDefinition) =
let attrName = "Microsoft.FSharp.Core.CompilationMappingAttribute"
let argName = "Microsoft.FSharp.Core.SourceConstructFlags"
getCustomAttributeArg td attrName argName
|> Option.map (fun arg -> arg.Value = box (int SourceConstructFlags.SumType))
|> defaultArg <| false
// is the type a case in a F# sum type?
let isFsCaseType (td:TypeDefinition) =
(td.DeclaringType <> null) && (isFsSumType td.DeclaringType)
// does the type have the GeneratedCodeAttribute?
let hasGeneratedCodeAttribute (td:TypeDefinition) =
let attrName = "System.CodeDom.Compiler.GeneratedCodeAttribute"
getCustomAttribute td attrName |> Option.isSome
/// Return true if the type name was generated by the compiler
let hasGeneratedTypeName (s:string) =
s.Contains("__") // C#
|| s.Contains("<") // C#
|| s.Contains("@") // F#
|| s.Contains("$") // F#
/// Return true if the type was authored by a human
let isAuthored td =
(td |> hasGeneratedCodeAttribute |> not) &&
(td |> isFsCaseType |> not) &&
(td.FullName |> hasGeneratedTypeName |> not)
/// a filter for nulls
let isNotNull o = (o<>null)
/// True if type reference is external to the dependant
/// This is caused by it being in a different assembly, or if excludeCore is true, by being in the Microsoft namespace
let isExternalRef (dependant:TypeDefinition) excludeCore (tr:TypeReference) =
// hack to handle nulls
let trScopeName =
if tr = null then ""
else if tr.Scope = null then ""
else tr.Scope.Name
match dependant.Scope.Name with
| s when s <> trScopeName -> true
| s when excludeCore && isCoreName s -> true
| _ -> false
/// Make a dependency from a TypeReference
let toDependency isExternal (tr:TypeReference) =
if isExternal tr
then typeName tr |> External
else typeName tr |> Internal
/// Given a TypeReference, return it and its generic arguments as well (recursively)
/// E.g IList<Option<String>> expands to { IList<>; Option<>; String }
let rec refWithGenericParams (tr:TypeReference) = seq {
match tr with
| :? GenericInstanceType as g ->
yield tr
for arg in g.GenericArguments do yield! refWithGenericParams arg
| _ ->
yield tr // just the typeRef
}
/// Get a list of all types directly referenced by public elements of a TypeDefinition
/// Not expanded to include generic params
let directPublicRefs (td:TypeDefinition) = seq {
yield td.BaseType
for int in td.Interfaces do
yield int
for fd in td.Fields do
if fd.IsPublic then yield fd.FieldType
// properties are included in methods
for md in td.Methods do
if md.IsPublic then
for parmd in md.Parameters do yield parmd.ParameterType
yield md.ReturnType
}
/// Get a list of all types directly or indirectly referenced by public elements of a TypeDefinition
let publicRefs (td:TypeDefinition) =
if td.IsPublic
then
td |> directPublicRefs |> Seq.collect refWithGenericParams
else
Seq.empty // nothing for private types
/// Create a visitor interface for the Cecil.Rocks.ILParser
/// It accumulates type references in a typeRefs param
let implVisitor typeRefs =
{new Mono.Cecil.Rocks.IILVisitor with
member this.OnInlineNone (_) = ()
member this.OnInlineSByte (_, _) = ()
member this.OnInlineByte (_, _) = ()
member this.OnInlineInt32 (_, _) = ()
member this.OnInlineInt64 (_, _) = ()
member this.OnInlineSingle (_, _) = ()
member this.OnInlineDouble (_, _) = ()
member this.OnInlineString (_, _) = ()
member this.OnInlineBranch (_, _) = ()
member this.OnInlineSwitch (_, _) = ()
member this.OnInlineSignature (_, _) = ()
member this.OnInlineVariable (_, variable) =
if variable <> null then
typeRefs := variable.VariableType :: !typeRefs; ()
member this.OnInlineArgument (_, parameter) =
if parameter <> null then
typeRefs := parameter.ParameterType :: !typeRefs; ()
member this.OnInlineType (_, t) =
if t <> null then
typeRefs := t :: !typeRefs; ()
member this.OnInlineField (_, field) =
if field <> null then
typeRefs := field.DeclaringType :: !typeRefs; ()
member this.OnInlineMethod (_, m)=
if m <> null then
typeRefs := m.DeclaringType :: !typeRefs; ()
}
/// Get a list of all types directly referenced by the implementation of a method
let directMethodRefs (md:MethodDefinition) =
let typeRefs = ref []
let visitor = implVisitor typeRefs
try
Mono.Cecil.Rocks.ILParser.Parse(md,visitor)
with
| ex -> () // ILParser can throw NRE -- ignore for our purposes
!typeRefs
/// Get a list of all types directly referenced by the implementation of all methods of a TypeDefinition
/// Not expanded to include generic params
let directImplRefs (td:TypeDefinition) = seq {
for fd in td.Fields do
yield fd.FieldType
for md in td.Methods do
yield! directMethodRefs md
}
/// Get a set of all types directly or indirectly referenced in the implementation of the methods of a TypeDefinition
let implRefs td =
td |> directImplRefs |> Seq.collect refWithGenericParams
/// Create a TypeWithDependencies from a TypeDefinition by gathering all its dependencies
let toTypeWithDependencies excludeCore td =
let isExternal = isExternalRef td excludeCore
let makeDep = toDependency isExternal
{
typeDef = td |> typeName
isAuthored = td |> isAuthored
publicDeps = td |> publicRefs |> Seq.filter isNotNull |> Seq.map makeDep |> Set.ofSeq
implDeps = td |> implRefs |> Seq.filter isNotNull |> Seq.map makeDep |> Set.ofSeq
}
// recursively get all the types under a parent type
let rec withAllChildTypes (parent:TypeDefinition) = [
yield parent
for child in parent.NestedTypes do
yield! child |> withAllChildTypes
]
/// Construct a TopLevelType from a top level TypeDefinition
let topLevelType excludeCore td =
// convert the typeDefs to Dependants
let nestedTypes =
td
|> withAllChildTypes
|> List.map (toTypeWithDependencies excludeCore)
|> Set.ofList
{
name = td.FullName |> TltName
types = nestedTypes
}
/// Get all authored top level types from an assembly
let topLevelTypes excludeCore assemblyFileName =
Mono.Cecil.AssemblyDefinition.ReadAssembly(fileName=assemblyFileName).MainModule.Types
|> Seq.filter (isNotCoreType excludeCore)
|> Seq.filter isAuthored
|> Seq.map (topLevelType excludeCore)
/// Get the code size for an assembly.
/// Count instructions in non-core methods only.
let codeSize excludeCore assemblyFileName =
Mono.Cecil.AssemblyDefinition.ReadAssembly(fileName=assemblyFileName).MainModule.Types
|> Seq.filter (isNotCoreType excludeCore)
|> Seq.collect withAllChildTypes
|> Seq.collect (fun td-> td.Methods)
|> Seq.filter (fun md-> md.HasBody)
|> Seq.map (fun md-> md.Body.CodeSize)
|> Seq.sum
// ================================
// Analyze dependencies between top level types
// ================================
module TopLevelTypeDependencies =
/// Create a reverse lookup from type to top level type as a prerequisite for dependency analysis.
/// Each TypeName points to the TopLevelType it is contained in.
let createLookup tlts =
let childParentTuples tlt =
tlt.types
|> Seq.map (fun t -> t.typeDef,tlt.name)
tlts
|> Seq.collect childParentTuples
|> Map.ofSeq
/// Map the low-level types to corresponding top-level types
/// low-level types that are external are ignored
let mapDependency lookup =
function
| Internal lowLevelType ->
Map.tryFind lowLevelType lookup
| External lowLevelType ->
None
/// Map a top-level type to a DependencySet
let toDependencySet lookup getDeps tlt =
let allDeps =
tlt.types
|> Set.map getDeps // get each low-level type's dependencies
|> Set.unionMany // combine into one set, removing dups
|> Seq.map (mapDependency lookup) // lookup to get corresponding top level type (maybe)
|> Seq.choose id // convert from option to TltName
|> Set.ofSeq // remove dups again
|> Set.remove tlt.name // remove any self-reference
{dependent=tlt.name; dependencies=allDeps}
/// Convert a list of top level types to a list of DependencySets, using the public dependencies
let publicDependencies topLevelTypes =
let lookup = createLookup topLevelTypes
let getDeps t = t.publicDeps
let map = toDependencySet lookup getDeps
topLevelTypes |> Seq.map map
/// Convert a list of top level types to a list of DependencySets, using the all dependencies, including private and implementation
let allDependencies topLevelTypes =
let lookup = createLookup topLevelTypes
let getDeps t = Set.union t.publicDeps t.implDeps
let map = toDependencySet lookup getDeps
topLevelTypes |> Seq.map map
// ================================
// Analyze the cyclic dependencies using QuickGraph
// ================================
module CyclicDependencies =
open QuickGraph
open QuickGraph.Algorithms
open System.Collections.Generic
/// Convert a list of depSets into a graph
let toGraph depSets =
let createEdge source target =
new QuickGraph.SEdge<_>(source, target)
let edges (depSet:DependencySet) =
depSet.dependencies
|> Set.toSeq // must convert back to seq type
|> Seq.map (createEdge depSet.dependent)
let allEdges = depSets |> Seq.collect edges
QuickGraph.GraphExtensions.ToAdjacencyGraph(edges=allEdges)
/// get the strongly connected components of the dependencySets as a collection of TopLevelType sets
let scc depSets =
let graph = depSets |> toGraph
// The components are returned as a (type->index) dictionary,
// where all types in a component have the same index
let componentDict = new Dictionary<TltName,int>() :> IDictionary<_,_>
let componentDictRef = ref componentDict
let componentCount = graph.StronglyConnectedComponents(componentDictRef)
let components =
!componentDictRef
|> Seq.groupBy (fun kvp -> kvp.Value) // the index
|> Seq.map (fun (k,seq) ->
seq
|> Seq.map (fun kvp -> kvp.Key) // extract the type
|> Set.ofSeq // convert to a set
)
components
/// get the set of other types connected to T, or empty
let connectedTo components t =
let whereContainsT = Set.contains t
components
|> Seq.filter whereContainsT // by definition, only one component contains t
|> Seq.collect id // extract the data, if any
|> Set.ofSeq // convert back into a set
/// For each dependency set, remove all types that are not in the same
/// strongly connected component, leaving only the cyclic dependencies
/// Also remove trivial sets (size=1)
let toCyclicDependencies depSets =
// get all the components
let components = scc depSets
// remove non-connected
let intersectWithScc depSet =
let connected = connectedTo components depSet.dependent
let dependencies' = Set.intersect depSet.dependencies connected
{depSet with dependencies= dependencies'}
let depSets' = depSets |> Seq.map intersectWithScc
depSets'
// ================================
// Generate the graph using GraphViz
// ================================
module GraphViz =
// change this as needed for your local environment
let graphVizPath = @"C:\Program Files (x86)\Graphviz2.30\bin\"
let getName (TltName n) =
sprintf "\"%s\"" n // be sure to quote the type name!
let toCsv sep strList =
match strList with
| [] -> ""
| _ -> List.reduce (fun s1 s2 -> s1 + sep + s2) strList
let writeDepSet writer depSet =
let fromNode = getName depSet.dependent
let toNodes =
depSet.dependencies
|> Seq.map getName
|> Seq.sort // make it more human readable
|> Seq.toList
|> toCsv "; "
fprintfn writer " %s -> { rank=none; %s }" fromNode toNodes
// Create a DOT file for graphviz to read.
let createDotFile dotFilename depSets =
use writer = new System.IO.StreamWriter(path=dotFilename)
fprintfn writer "digraph G {"
fprintfn writer " page=\"40,60\"; "
fprintfn writer " ratio=auto;"
fprintfn writer " rankdir=LR;"
fprintfn writer " fontsize=10;"
depSets
|> Seq.filter (fun ds -> ds.dependencies.Count > 0) // ignore empty
|> Seq.sort // make it more human readable
|> Seq.iter (writeDepSet writer)
fprintfn writer " }"
// shell out to run a command line program
let startProcessAndCaptureOutput cmd cmdParams =
let debug = false
if debug then
printfn "Process: %s %s" cmd cmdParams
let si = new System.Diagnostics.ProcessStartInfo(cmd, cmdParams)
si.UseShellExecute <- false
si.RedirectStandardOutput <- true
use p = new System.Diagnostics.Process()
p.StartInfo <- si
if p.Start() then
if debug then
use stdOut = p.StandardOutput
stdOut.ReadToEnd() |> printfn "%s"
printfn "Process complete"
else
printfn "Process failed"
/// Generate an image file from a DOT file
/// algo = dot, neato
/// format = gif, png, jpg, svg
let generateImageFile dotFilename algo format imgFilename =
let cmd = sprintf @"""%s%s.exe""" graphVizPath algo
let inFile = System.IO.Path.Combine(__SOURCE_DIRECTORY__,dotFilename)
let outFile = System.IO.Path.Combine(__SOURCE_DIRECTORY__,imgFilename)
let cmdParams = sprintf "-T%s -o\"%s\" \"%s\"" format outFile inFile
startProcessAndCaptureOutput cmd cmdParams
// ================================
// Various statistics
// ================================
module Stats =
/// a record to hold some stats
type TypeStats = {
topLevelTypeCount:int // number of top level types
authoredTypeCount:int // number of types explicitly authored
allTypeCount: int // number of all internal types
}
let typeStats topLevelTypes =
let topLevelTypeCount = topLevelTypes |> Seq.length
let authoredTypeCount =
topLevelTypes
|> Seq.collect (fun tlt -> tlt.types )
|> Seq.filter (fun td -> td.isAuthored)
|> Seq.length
let allTypeCount =
topLevelTypes
|> Seq.map (fun tlt -> Set.count tlt.types)
|> Seq.sum
{ topLevelTypeCount=topLevelTypeCount; authoredTypeCount=authoredTypeCount; allTypeCount=allTypeCount}
/// a record to hold some stats
type DependencyStats = {
depCount: int // number of dependency sets (same as number of top level types)
totalDepCount: int // sum of number of (internal) dependencies for all types
oneOrMoreDeps: int // number of top level types with one or more deps
threeOrMoreDeps: int // number of top level types with three or more deps
fiveOrMoreDeps: int // number of top level types with five or more deps
tenOrMoreDeps: int // number of top level types with ten or more deps
cycleCount:int // number of cycles
cycleParticipants: int // number of participants in cycles
maxComponentSize: int // max size of a strongly connected component
}
/// return the size of the largest component
let maxComponentSize components =
if Seq.isEmpty components
then 0 // max won't work on an empty set
else
components
|> Seq.map (fun set -> Set.count set)
|> Seq.max
let dependencyStats depSets =
let depCount = depSets |> Seq.length
let totalDepCount = depSets |> Seq.sumBy (fun depSet -> depSet.dependencies.Count)
let getCount max =
depSets
|> Seq.map (fun depSet -> depSet.dependencies.Count)
|> Seq.filter (fun count -> count >= max)
|> Seq.length
let oneOrMoreDeps = getCount 1
let threeOrMoreDeps = getCount 3
let fiveOrMoreDeps = getCount 5
let tenOrMoreDeps = getCount 10
let nonTrivialComponents = CyclicDependencies.scc depSets |> Seq.filter (fun set -> set.Count > 1)
let cycleCount = nonTrivialComponents |> Seq.length
let maxComponentSize = maxComponentSize nonTrivialComponents
let cyclicDeps = CyclicDependencies.toCyclicDependencies depSets |> Seq.filter (fun ds -> ds.dependencies.Count > 0) // ignore empty
let cycleParticipants = cyclicDeps |> Seq.length
{ depCount=depCount;
totalDepCount=totalDepCount; oneOrMoreDeps=oneOrMoreDeps;
threeOrMoreDeps=threeOrMoreDeps; fiveOrMoreDeps=fiveOrMoreDeps; tenOrMoreDeps=tenOrMoreDeps;
cycleCount=cycleCount; cycleParticipants=cycleParticipants; maxComponentSize=maxComponentSize }
/// a record to hold some stats
type FrequencyStats = {
value:int
count:int
}
let typeFrequency topLevelTypes =
let authCount tds =
tds
|> Seq.filter (fun td -> td.isAuthored)
|> Seq.length
let authoredTypeFrequencies =
topLevelTypes
|> Seq.groupBy (fun tlt -> authCount tlt.types)
|> Seq.map (fun (k,v) -> {value=k;count=Seq.length v} )
let allTypeFrequencies =
topLevelTypes
|> Seq.groupBy (fun tlt -> Set.count tlt.types)
|> Seq.map (fun (k,v) -> {value=k;count=Seq.length v} )
authoredTypeFrequencies,allTypeFrequencies
let depFrequency depSets =
depSets
|> Seq.groupBy (fun depSet -> Set.count depSet.dependencies)
|> Seq.map (fun (k,v) -> {value=k;count=Seq.length v} )
// ================================
// For REPL or debugging
// ================================
module Output =
open Stats
let printDependencies depSets =
let printDependencySet depSet =
depSet.dependent |> printfn "%A"
depSet.dependencies |> Seq.toList |> List.sort |> List.iter (printfn "\t%A")
depSets
|> Seq.sortBy (fun depSet -> depSet.dependent)
|> Seq.iter printDependencySet
let printStats projectName codeSize typeStats publicDepStats allDepStats =
let codeToTopTypeRatio = codeSize / typeStats.topLevelTypeCount
let codeToAuthoredTypeRatio = codeSize / typeStats.authoredTypeCount
let codeToAllTypeRatio = codeSize / typeStats.allTypeCount
let topToAllRatio = float typeStats.topLevelTypeCount / float typeStats.allTypeCount
let topToAuthoredRatio = float typeStats.topLevelTypeCount / float typeStats.authoredTypeCount
printfn "==========================================="
printfn "Stats for %s" projectName
printfn "==========================================="
printfn " - CodeSize=%i TopLevelTypes=%i AllTypes=%i" codeSize typeStats.topLevelTypeCount typeStats.allTypeCount
printfn " - Instructions per top level type=%i (Instructions per authored type=%i)" codeToTopTypeRatio codeToAuthoredTypeRatio
printfn " - Ratio of top level types to authored types = %.2f " topToAuthoredRatio
printfn " - Ratio of top level types to all types = %.2f " topToAllRatio
let avgDepCount stats = float stats.totalDepCount / float typeStats.topLevelTypeCount
let ( <%> )top bottom = float top * 100.0 / float bottom
let pcOneOrMoreDeps stats = stats.oneOrMoreDeps <%> typeStats.topLevelTypeCount
let pcThreeOrMoreDeps stats = stats.threeOrMoreDeps <%> typeStats.topLevelTypeCount
let pcFiveOrMoreDeps stats = stats.fiveOrMoreDeps <%> typeStats.topLevelTypeCount
let pcTenOrMoreDeps stats = stats.tenOrMoreDeps <%> typeStats.topLevelTypeCount
printfn "Analysis of implementation dependencies for %s" projectName
printfn " - Avg # of dependencies=%.2f One or more=%.1f%%, 3 or more=%.1f%%, 5 or more=%.1f%%, 10 or more=%.1f%%" (avgDepCount allDepStats) (pcOneOrMoreDeps allDepStats) (pcThreeOrMoreDeps allDepStats) (pcFiveOrMoreDeps allDepStats) (pcTenOrMoreDeps allDepStats)
printfn " - Cycle count=%i cycleParticipants=%i maxComponentSize=%i" allDepStats.cycleCount allDepStats.cycleParticipants allDepStats.maxComponentSize
printfn "Analysis of public dependencies for %s" projectName
printfn " - Avg # of dependencies=%.2f One or more=%.1f%%, 3 or more=%.1f%%, 5 or more=%.1f%%, 10 or more=%.1f%%" (avgDepCount publicDepStats) (pcOneOrMoreDeps publicDepStats) (pcThreeOrMoreDeps publicDepStats) (pcFiveOrMoreDeps publicDepStats) (pcTenOrMoreDeps publicDepStats)
printfn " - Cycle count=%i cycleParticipants=%i maxComponentSize=%i" publicDepStats.cycleCount publicDepStats.cycleParticipants publicDepStats.maxComponentSize
let writeFrequenciesToFile projectName extension frequencies =
let filename = projectName + extension
use writer = new System.IO.StreamWriter(path=filename)
fprintfn writer "Project,Value,Count"
let writeFreq freq =
fprintfn writer "%s,%i,%i" projectName freq.value freq.count
frequencies
|> Seq.sortBy (fun f -> f.value)
|> Seq.iter writeFreq
// ================================
// Main functions
// ================================
/// Analysis should not include the F# core types except
/// for the projects explicitly listed
let ignoreCoreTypes projectName =
projectName <> "fsCore" && projectName <> "fsPowerPack"
let analyzeAndGenerate projectName assemblyName =
let excludeCore = ignoreCoreTypes projectName
// analyze
let topLevelTypes = AssemblyTypes.topLevelTypes excludeCore assemblyName
let publicDeps = TopLevelTypeDependencies.publicDependencies topLevelTypes
let publicCycles = CyclicDependencies.toCyclicDependencies publicDeps
let allDeps = TopLevelTypeDependencies.allDependencies topLevelTypes
let allCycles = CyclicDependencies.toCyclicDependencies allDeps
// stats
let codeSize = AssemblyTypes.codeSize excludeCore assemblyName
let typeStats = Stats.typeStats topLevelTypes
let publicDepStats = Stats.dependencyStats publicDeps
let allDepStats = Stats.dependencyStats allDeps
do Output.printStats projectName codeSize typeStats publicDepStats allDepStats
let generateFile depSet extension =
// create DOT file
let dotFilename = projectName + extension
GraphViz.createDotFile dotFilename depSet
// create SVG file
let svgFilename = dotFilename + ".svg"
GraphViz.generateImageFile dotFilename "dot" "svg" svgFilename
do generateFile publicDeps ".public.dot"
do generateFile publicCycles ".public.cycles.dot"
do generateFile allDeps ".all.dot"
do generateFile allCycles ".all.cycles.dot"
let tabularStats projects =
printfn "Project,CodeSize,TopLevelTypes,AuthoredTypes,AllTypes,AllTotalDepCount,AllOneOrMoreDepCount,AllThreeOrMoreDepCount,AllFiveOrMoreDepCount,AllTenOrMoreDepCount,AllMaxComponentSize,AllCycleCount,AllCycleParticipants,PubTotalDepCount,PubOneOrMoreDepCount,PubThreeOrMoreDepCount,PubFiveOrMoreDepCount,PubTenOrMoreDepCount,PubMaxComponentSize,PubCycleCount,PubCycleParticipants"
for (projectName,assemblyName) in projects do
let excludeCore = ignoreCoreTypes projectName
// analyze
let topLevelTypes = AssemblyTypes.topLevelTypes excludeCore assemblyName
let publicDeps = TopLevelTypeDependencies.publicDependencies topLevelTypes
let publicCycles = CyclicDependencies.toCyclicDependencies publicDeps
let allDeps = TopLevelTypeDependencies.allDependencies topLevelTypes
let allCycles = CyclicDependencies.toCyclicDependencies allDeps
// stats
let codeSize = AssemblyTypes.codeSize excludeCore assemblyName
let typeStats = Stats.typeStats topLevelTypes
let publicDepStats = Stats.dependencyStats publicDeps
let allDepStats = Stats.dependencyStats allDeps
printfn "%s,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i" projectName codeSize typeStats.topLevelTypeCount typeStats.authoredTypeCount typeStats.allTypeCount allDepStats.totalDepCount allDepStats.oneOrMoreDeps allDepStats.threeOrMoreDeps allDepStats.fiveOrMoreDeps allDepStats.tenOrMoreDeps allDepStats.maxComponentSize allDepStats.cycleCount allDepStats.cycleParticipants publicDepStats.totalDepCount publicDepStats.oneOrMoreDeps publicDepStats.threeOrMoreDeps publicDepStats.fiveOrMoreDeps publicDepStats.tenOrMoreDeps publicDepStats.maxComponentSize publicDepStats.cycleCount publicDepStats.cycleParticipants
let frequenciesStats projects =
for (projectName,assemblyName) in projects do
let excludeCore = ignoreCoreTypes projectName
// analyze
let topLevelTypes = AssemblyTypes.topLevelTypes excludeCore assemblyName
let allDeps = TopLevelTypeDependencies.allDependencies topLevelTypes
// stats
let authored,all = Stats.typeFrequency topLevelTypes
let deps = Stats.depFrequency allDeps
Output.writeFrequenciesToFile projectName ".authored.freq.csv" authored
Output.writeFrequenciesToFile projectName ".types.freq.csv" all
Output.writeFrequenciesToFile projectName ".deps.freq.csv" deps
// ===============================================
// C# Projects
// ===============================================
// C# Project - Mono.Cecil
// Install-Package Mono.Cecil -Version 0.9.5.4
let cecil = @"packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll"
// C# Project - NUnit.Framework
// Install-Package NUnit -Version 2.6.2
let nunit = @"packages\NUnit.2.6.2\lib\nunit.framework.dll"
// C# Project - Nancy
// Install-Package Nancy -Version 0.17.1
let nancy = @"packages\Nancy.0.17.1\lib\net40\Nancy.dll"
// C# Project - YamlDotNet
// Install-Package YamlDotNet.Core -Version 1.3.0
let yamlDotNet = @"packages\YamlDotNet.Core.1.3.0\lib\YamlDotNet.Core.dll"
// C# Project - SpecFlow
// Install-Package SpecFlow -Version 1.9.0
let specFlow = @"packages\SpecFlow.1.9.0\lib\net35\TechTalk.SpecFlow.dll"
// C# Project - SignalR
// Install-Package Microsoft.AspNet.SignalR.Core -Version 1.1.1
let signalR = @"packages\Microsoft.AspNet.SignalR.Core.1.1.1\lib\net40\Microsoft.AspNet.SignalR.Core.dll"
// C# Project - JSON.NET
// Install-Package Newtonsoft.Json -Version 5.0.5
let jsonDotNet = @"packages\Newtonsoft.Json.5.0.5\lib\net40\Newtonsoft.Json.dll"
// C# Project - EntityFramework
// Install-Package EntityFramework -Version 5.0.0
let ef = @"packages\EntityFramework.5.0.0\lib\net40\EntityFramework.dll"
// C# Project - Elmah
// Install-Package elmah -Version 1.2.2
let elmah = @"packages\elmah.corelibrary.1.2.2\lib\Elmah.dll"
// C# Project - Nuget
// Install-Package Nuget.Core -Version 2.5.0
let nuget = @"packages\Nuget.Core.2.5.0\lib\net40-Client\NuGet.Core.dll"
// C# Project - FParsecCS
// Install-Package FParsec -Version 0.9.2.0
let fparsecCS = @"packages\FParsec.0.9.2.0\lib\net40\FParsecCS.dll"
// C# Project - Moq
// Install-Package Moq -Version 4.0.10827
let moq = @"packages\Moq.4.0.10827\lib\NET40\Moq.dll"
// C# Project - NDepend
// Manual install needed from http://ndepend.com
let ndepend = @"packages\NDepend\NDepend.Framework.dll"
let ndependPlat = @"packages\NDepend\NDepend.Platform.DotNet.dll"
// ===============================================
// F# Projects
// ===============================================
// F# Project - FSharp.Core
// Install-Package FSharp.Core -Version 4.0.0
let fsCore = @"packages\FSharp.Core.4.0.0\lib\FSharp.Core.dll"
// F# Project - FSharp.PowerPack
// Install-Package FSPowerPack.Community -Version 3.0.0.0
let fsPowerPack = @"packages\FSPowerPack.Core.Community.3.0.0.0\lib\net40\Fsharp.PowerPack.dll"
// F# Project - FParsec
// Install-Package FParsec -Version 0.9.2.0
let fParsec = @"packages\FParsec.0.9.2.0\lib\net40\FParsec.dll"
// F# Project - canopy
// Install-Package canopy -Version 0.7.5
let canopy = @"packages\canopy.0.7.5\lib\canopy.dll"
// F# Project - FsSql
// Install-Package FsSql -Version 0.1.0
let fsSql = @"packages\FsSql.0.1.0\lib\FsSql.dll"
// F# Project - FsYaml
// Install-Package FsYaml -Version 1.1.2
let fsYaml = @"packages\FsYaml.1.1.2\lib\net40\FsYaml.dll"
// F# Project - Storm
// Download from http://storm.codeplex.com/releases/view/18871
let storm = @"Storm.exe"
// F# Project - FsharpxCore
// Install-Package FSharpx.Core -Version 1.8.23
let fsxCore = @"packages\FSharpx.Core.1.8.23\lib\40\FSharpx.Core.dll"
// F# Project - TickSpec
// Install-Package TickSpec -Version 1.0.0.1
let tickSpec = @"packages\TickSpec.1.0.0.1\Lib\net40\TickSpec.dll"
// F# Project - Websharper
// Install-Package WebSharper -Version 2.4.85.235
let websharper = @"packages\WebSharper.2.4.85.235\IntelliFactory.WebSharper.dll"
let websharperCore = @"packages\WebSharper.2.4.85.235\IntelliFactory.WebSharper.Core.dll"
let websharperFormlet = @"packages\WebSharper.2.4.85.235\IntelliFactory.WebSharper.Formlet.dll"
let websharperHtml = @"packages\WebSharper.2.4.85.235\IntelliFactory.WebSharper.Html.dll"
// F# Project - FsUnit
// Install-Package FsUnit -Version 1.2.1.0
let fsUnit = @"packages\FsUnit.1.2.1.0\Lib\Net40\FsUnit.NUnit.dll"
// F# Project - Foq
// Install-Package Foq -Version 0.9
let foq = @"packages\Foq.0.9\Lib\net40\Foq.dll"
let csProjects = [
("ef",ef)
("jsonDotNet",jsonDotNet)
("nancy",nancy)
("cecil",cecil)
("nuget",nuget)
("signalR",signalR)
("nunit",nunit)
("specFlow",specFlow)
("elmah",elmah)
("yamlDotNet",yamlDotNet)
("fparsecCS",fparsecCS)
("moq",moq)
("ndepend",ndepend)
("ndependPlat",ndependPlat)
]
let fsProjects = [
("fsxCore",fsxCore)
("fsCore",fsCore)
("fsPowerPack",fsPowerPack)
("storm",storm)
("fParsec",fParsec)
("websharper",websharper)
("tickSpec",tickSpec)
("websharperHtml", websharperHtml)
("canopy",canopy)
("fsYaml",fsYaml)
("fsSql",fsSql)
("fsUnit",fsUnit)
("foq",foq)
]
// ===============================================
// Generate SVG files
// ===============================================
analyzeAndGenerate "ef" ef
analyzeAndGenerate "jsonDotNet" jsonDotNet
analyzeAndGenerate "nancy" nancy
analyzeAndGenerate "cecil" cecil
analyzeAndGenerate "nuget" nuget
analyzeAndGenerate "signalR" signalR
analyzeAndGenerate "nunit" nunit
analyzeAndGenerate "specFlow" specFlow
analyzeAndGenerate "elmah" elmah
analyzeAndGenerate "yamlDotNet" yamlDotNet
analyzeAndGenerate "fparsecCS" fparsecCS
analyzeAndGenerate "moq" moq
analyzeAndGenerate "ndepend" ndepend
analyzeAndGenerate "ndependPlat" ndependPlat
analyzeAndGenerate "fsxCore" fsxCore
analyzeAndGenerate "fsCore" fsCore
analyzeAndGenerate "fsPowerPack" fsPowerPack
analyzeAndGenerate "storm" storm
analyzeAndGenerate "fParsec" fParsec
analyzeAndGenerate "websharper" websharper
analyzeAndGenerate "websharperCore" websharperCore
analyzeAndGenerate "websharperFormlet" websharperFormlet
analyzeAndGenerate "websharperHtml" websharperHtml
analyzeAndGenerate "tickSpec" tickSpec
analyzeAndGenerate "canopy" canopy
analyzeAndGenerate "fsYaml" fsYaml
analyzeAndGenerate "fsSql" fsSql
analyzeAndGenerate "fsUnit" fsUnit
analyzeAndGenerate "foq" foq
// ===============================================
// All stats tabulated
// ===============================================
tabularStats csProjects
tabularStats fsProjects
frequenciesStats csProjects
frequenciesStats fsProjects
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment