I am writing a validation tool that checks the versions of files referenced in a project. I want to use the same resolution process that MSBuild uses.
For example, Assembly.Load(..) requires a fully-qualified assembly name. However, in the project file, we may only have something like "System.Xml". MSBuild probably uses the project's target framework version and some other heuristics to decide which version of System.Xml to load.
How would you go about mimicking (or directly using) msbuild's assembly resolution process?
In other words, at run-time, I want to take the string "System.Xml", along with other info found in a .csproj file and find the same file that msbuild would find.
Reference assemblies are usually distributed with the Software Development Kit (SDK) of a particular platform or library. Using a reference assembly enables developers to build programs that target a specific library version without having the full implementation assembly for that version.
There is no way to determine which version of MSBuild was used. There is no days in the executable that says which version was used and nothing in them specific to MSBuild to use as a trail of breadcrumbs to determine it either. You dont even need MSBuild to build an executable.
The Microsoft Build Engine is a platform for building applications. This engine, which is also known as MSBuild, provides an XML schema for a project file that controls how the build platform processes and builds software. Visual Studio uses MSBuild, but MSBuild doesn't depend on Visual Studio.
Visual Studio determines the build order and calls into MSBuild separately (as needed), all completely under Visual Studio's control. Another difference arises when MSBuild is invoked with a solution file, MSBuild parses the solution file, creates a standard XML input file, evaluates it, and executes it as a project.
I had this problem today, and I found this old blog post on how to do it:
http://blogs.msdn.com/b/jomo_fisher/archive/2008/05/22/programmatically-resolve-assembly-name-to-full-path-the-same-way-msbuild-does.aspx
I tried it out, works great! I modified the code to find 4.5.1 versions of assemblies when possible, this is what I have now:
#if INTERACTIVE
#r "Microsoft.Build.Engine"
#r "Microsoft.Build.Framework"
#r "Microsoft.Build.Tasks.v4.0"
#r "Microsoft.Build.Utilities.v4.0"
#endif
open System
open System.Reflection
open Microsoft.Build.Tasks
open Microsoft.Build.Utilities
open Microsoft.Build.Framework
open Microsoft.Build.BuildEngine
/// Reference resolution results. All paths are fully qualified.
type ResolutionResults = {
referencePaths:string array
referenceDependencyPaths:string array
relatedPaths:string array
referenceSatellitePaths:string array
referenceScatterPaths:string array
referenceCopyLocalPaths:string array
suggestedBindingRedirects:string array
}
let resolve (references:string array, outputDirectory:string) =
let x = { new IBuildEngine with
member be.BuildProjectFile(projectFileName, targetNames, globalProperties, targetOutputs) = true
member be.LogCustomEvent(e) = ()
member be.LogErrorEvent(e) = ()
member be.LogMessageEvent(e) = ()
member be.LogWarningEvent(e) = ()
member be.ColumnNumberOfTaskNode with get() = 1
member be.ContinueOnError with get() = true
member be.LineNumberOfTaskNode with get() = 1
member be.ProjectFileOfTaskNode with get() = "" }
let rar = new ResolveAssemblyReference()
rar.BuildEngine <- x
rar.IgnoreVersionForFrameworkReferences <- true
rar.TargetFrameworkVersion <- "v4.5.1"
rar.TargetedRuntimeVersion <- "v4.5.1"
rar.TargetFrameworkDirectories <- [||] //[|@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\"|]
rar.Assemblies <- [|for r in references -> new Microsoft.Build.Utilities.TaskItem(r) :> ITaskItem|]
rar.AutoUnify <- true
rar.SearchPaths <- [| "{CandidateAssemblyFiles}"
"{HintPathFromItem}"
"{TargetFrameworkDirectory}"
// "{Registry:Software\Microsoft\.NetFramework,v3.5,AssemblyFoldersEx}"
"{AssemblyFolders}"
"{GAC}"
"{RawFileName}"
outputDirectory |]
rar.AllowedAssemblyExtensions <- [| ".exe"; ".dll" |]
rar.TargetProcessorArchitecture <- "x86"
if not (rar.Execute()) then
failwith "Could not resolve"
{
referencePaths = [| for p in rar.ResolvedFiles -> p.ItemSpec |]
referenceDependencyPaths = [| for p in rar.ResolvedDependencyFiles -> p.ItemSpec |]
relatedPaths = [| for p in rar.RelatedFiles -> p.ItemSpec |]
referenceSatellitePaths = [| for p in rar.SatelliteFiles -> p.ItemSpec |]
referenceScatterPaths = [| for p in rar.ScatterFiles -> p.ItemSpec |]
referenceCopyLocalPaths = [| for p in rar.CopyLocalFiles -> p.ItemSpec |]
suggestedBindingRedirects = [| for p in rar.SuggestedRedirects -> p.ItemSpec |]
}
[<EntryPoint>]
let main argv =
try
let s = resolve([| "System"
"System.Data"
"System.Core, Version=4.0.0.0"
"Microsoft.SqlServer.Replication" |], "")
printfn "%A" s.referencePaths
finally
ignore (System.Console.ReadKey())
0
If you target the Framework version you want to be compatible with instead of targeting 3.5, Visual Studio 2008 SP1 and FxCop 1.36 RTM added rule CA 1903: Use only API from targeted framework to ensure you stay compatible with the target framework version. Turning that rule on and treating it as an error will fail your Build and provide the behavior you want.
Here is sample code demonstrating a violation when you are targeting framework version 2:
using System.Runtime;
class Program
{
static void Main()
{
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With