PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/compiler/2.0/Aug2011/src/fsharp/ReferenceResolution.fs

#
F# | 349 lines | 280 code | 37 blank | 32 comment | 39 complexity | 2e2ea405c427ad017cd50d2cb3a1d3c8 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. namespace Microsoft.FSharp.Compiler
  2. module internal MSBuildResolver =
  3. open System
  4. open Microsoft.Build.Tasks
  5. open Microsoft.Build.Utilities
  6. open Microsoft.Build.Framework
  7. open Microsoft.Build.BuildEngine
  8. open System.IO
  9. exception ResolutionFailure
  10. type ResolvedFrom =
  11. | AssemblyFolders
  12. | AssemblyFoldersEx
  13. | TargetFrameworkDirectory
  14. | RawFileName
  15. | GlobalAssemblyCache
  16. | Path of string
  17. | Unknown
  18. type ResolvedFile = {
  19. itemSpec:string
  20. resolvedFrom:ResolvedFrom
  21. fusionName:string
  22. version:string
  23. redist:string
  24. baggage:string
  25. }
  26. with override this.ToString() = sprintf "ResolvedFile(%s)" this.itemSpec
  27. type ResolutionResults = {
  28. resolvedFiles:ResolvedFile array
  29. referenceDependencyPaths:string array
  30. relatedPaths:string array
  31. referenceSatellitePaths:string array
  32. referenceScatterPaths:string array
  33. referenceCopyLocalPaths:string array
  34. suggestedBindingRedirects:string array
  35. }
  36. let ReplaceFrameworkVariables(dirs) =
  37. let windowsFramework = System.Environment.GetEnvironmentVariable("windir")+ @"\Microsoft.NET\Framework"
  38. let referenceAssemblies = System.Environment.GetEnvironmentVariable("ProgramFiles")+ @"\Reference Assemblies\Microsoft\Framework\.NETFramework"
  39. dirs|>List.map(fun (d:string)->d.Replace("{WindowsFramework}",windowsFramework).Replace("{ReferenceAssemblies}",referenceAssemblies))
  40. // Q: Why is there a compilation difference between fsc.exe-2.0 and fsc.exe-4.0? Shouldn't the resolve behavior be the same?
  41. // A: Two reasons
  42. // 1) For 4.0 we don't know the version number in this path: c:\Windows\Microsoft.NET\Framework\v4.0.xxxxx
  43. // 2) Fsc.exe-2.0 by design cannot target 4.0
  44. #if FX_ATLEAST_40
  45. /// The .NET runtime version that F# was built against.
  46. let DotNetRuntime = "v4.0.30319"
  47. /// The short version (like 4.0) that F# was built against.
  48. let DotNetRuntimeShort = "v4.0"
  49. /// Locations of .NET framework assemblies.
  50. let DotNetFrameworkDirectories(excludeNonExecutableAssemblies) =
  51. if excludeNonExecutableAssemblies
  52. then ReplaceFrameworkVariables([@"{WindowsFramework}\"+DotNetRuntime])
  53. else ReplaceFrameworkVariables([@"{ReferenceAssemblies}\"+DotNetRuntimeShort; @"{WindowsFramework}\"+DotNetRuntime])
  54. #endif
  55. /// Derive the target framework directories.
  56. let DeriveTargetFrameworkDirectories
  57. (targetFrameworkVersion:string, // e.g. v2.0, v3.0, v3.5, v4.0 etc
  58. excludeNonExecutableAssemblies:bool, // True when the assembly must be executable and not just a stub meta assembly.
  59. logmessage:string->unit) =
  60. let targetFrameworkVersion =
  61. if not(targetFrameworkVersion.StartsWith("v",StringComparison.Ordinal)) then "v"^targetFrameworkVersion
  62. else targetFrameworkVersion
  63. let FrameworkStartsWith(short) =
  64. targetFrameworkVersion.StartsWith(short,StringComparison.Ordinal)
  65. let result =
  66. if FrameworkStartsWith("v1.0") then ReplaceFrameworkVariables([@"{WindowsFramework}\v1.0.3705"])
  67. else if FrameworkStartsWith("v1.1") then ReplaceFrameworkVariables([@"{WindowsFramework}\v1.1.4322"])
  68. else if FrameworkStartsWith("v2.0") then ReplaceFrameworkVariables([@"{WindowsFramework}\v2.0.50727"])
  69. else if FrameworkStartsWith("v3.0") then ReplaceFrameworkVariables([@"{ReferenceAssemblies}\v3.0"; @"{WindowsFramework}\v3.0"; @"{WindowsFramework}\v2.0.50727"])
  70. else if FrameworkStartsWith("v3.5") then ReplaceFrameworkVariables([@"{ReferenceAssemblies}\v3.5"; @"{WindowsFramework}\v3.5"; @"{ReferenceAssemblies}\v3.0"; @"{WindowsFramework}\v3.0"; @"{WindowsFramework}\v2.0.50727"])
  71. #if FX_ATLEAST_40
  72. else DotNetFrameworkDirectories(excludeNonExecutableAssemblies)
  73. #else
  74. else (ignore(excludeNonExecutableAssemblies); [])
  75. #endif
  76. let result = result |> Array.ofList
  77. logmessage (sprintf "Derived target framework directories for version %s are: %s" targetFrameworkVersion (String.Join(",", result)))
  78. result
  79. /// Decode the ResolvedFrom code from MSBuild.
  80. let DecodeResolvedFrom(resolvedFrom:string) : ResolvedFrom =
  81. let Same a b =
  82. String.CompareOrdinal(a,b) = 0
  83. match resolvedFrom with
  84. | r when Same "{RawFileName}" r -> RawFileName
  85. | r when Same "{GAC}" r -> GlobalAssemblyCache
  86. | r when Same "{TargetFrameworkDirectory}" r -> TargetFrameworkDirectory
  87. | r when Same "{AssemblyFolders}" r -> AssemblyFolders
  88. | r when r.Length >= 10 && Same "{Registry:" (r.Substring(0,10)) -> AssemblyFoldersEx
  89. | r -> Path r
  90. type ErrorWarningCallbackSig = ((*code:*)string->(*message*)string->unit)
  91. type ResolutionEnvironment = CompileTimeLike | RuntimeLike | DesigntimeLike
  92. type Foregrounded =
  93. | ForegroundedMessage of string
  94. | ForegroundedError of string * string
  95. | ForegroundedWarning of string * string
  96. let ResolveCore(
  97. resolutionEnvironment: ResolutionEnvironment,
  98. references:(string*(*baggage*)string)[],
  99. targetFrameworkVersion:string,
  100. targetFrameworkDirectories:string list,
  101. targetProcessorArchitecture:string,
  102. outputDirectory:string,
  103. fsharpBinariesDir:string,
  104. explicitIncludeDirs:string list,
  105. implicitIncludeDir:string,
  106. frameworkRegistryBase:string,
  107. assemblyFoldersSuffix:string,
  108. assemblyFoldersConditions:string,
  109. allowRawFileName:bool,
  110. logmessage:string->unit,
  111. logwarning:ErrorWarningCallbackSig,
  112. logerror:ErrorWarningCallbackSig ) =
  113. // Message Foregrounding:
  114. // In version 4.0 MSBuild began calling log methods on a background (non-UI) thread. If there is an exception thrown from
  115. // logmessage, logwarning or logerror then it would kill the process.
  116. // The fix is to catch these exceptions and log the rest of the messages to a list to output at the end.
  117. // It looks simpler to always just accumulate the messages during resolution and show them all at the end, but then
  118. // we couldn't see the messages as resolution progresses.
  119. let foregrounded = ref []
  120. let backgroundException : exn option ref = ref None
  121. let logmessage message =
  122. match !backgroundException with
  123. | Some _ -> foregrounded := ForegroundedMessage(message) :: !foregrounded
  124. | None ->
  125. try
  126. logmessage message
  127. with e ->
  128. backgroundException := Some(e)
  129. foregrounded := ForegroundedMessage(message) :: !foregrounded
  130. let logwarning code message =
  131. match !backgroundException with
  132. | Some _ -> foregrounded := ForegroundedWarning(code,message) :: !foregrounded
  133. | None ->
  134. try
  135. logwarning code message
  136. with e ->
  137. backgroundException := Some(e)
  138. foregrounded := ForegroundedWarning(code,message) :: !foregrounded
  139. let logerror code message =
  140. match !backgroundException with
  141. | Some _ -> foregrounded := ForegroundedError(code,message) :: !foregrounded
  142. | None ->
  143. try
  144. logerror code message
  145. with e ->
  146. backgroundException := Some(e)
  147. foregrounded := ForegroundedError(code,message) :: !foregrounded
  148. let engine = { new IBuildEngine with
  149. member be.BuildProjectFile(projectFileName, targetNames, globalProperties, targetOutputs) = true
  150. member be.LogCustomEvent(e) = logmessage e.Message
  151. member be.LogErrorEvent(e) = logerror e.Code e.Message
  152. member be.LogMessageEvent(e) = logmessage e.Message
  153. member be.LogWarningEvent(e) = logwarning e.Code e.Message
  154. member be.ColumnNumberOfTaskNode with get() = 1
  155. member be.LineNumberOfTaskNode with get() = 1
  156. member be.ContinueOnError with get() = true
  157. member be.ProjectFileOfTaskNode with get() = "" }
  158. let rar = new ResolveAssemblyReference()
  159. rar.BuildEngine <- engine
  160. // Derive target framework directory if none was supplied.
  161. let excludeNonExecutableAssemblies = (resolutionEnvironment = RuntimeLike)
  162. let targetFrameworkDirectories =
  163. if targetFrameworkDirectories=[] then DeriveTargetFrameworkDirectories(targetFrameworkVersion,excludeNonExecutableAssemblies,logmessage)
  164. else targetFrameworkDirectories |> Array.ofList
  165. // Filter for null and zero length, and escape backslashes so legitimate path characters aren't mistaken for
  166. // escape characters (E.g., ".\r.dll")
  167. let explicitIncludeDirs = explicitIncludeDirs |> List.filter(fun eid->not(String.IsNullOrEmpty(eid)))
  168. let references = references |> Array.filter(fun (path,_)->not(String.IsNullOrEmpty(path))) // |> Array.map (fun (path,baggage) -> (path.Replace("\\","\\\\"),baggage))
  169. rar.TargetFrameworkDirectories <- targetFrameworkDirectories
  170. rar.FindRelatedFiles <- false
  171. rar.FindDependencies <- false
  172. rar.FindSatellites <- false
  173. rar.FindSerializationAssemblies <- false
  174. rar.TargetProcessorArchitecture <- targetProcessorArchitecture
  175. rar.Assemblies <- [|for (referenceName,baggage) in references ->
  176. let item = new Microsoft.Build.Utilities.TaskItem(referenceName)
  177. item.SetMetadata("Baggage", baggage)
  178. item:>ITaskItem|]
  179. let rawFileNamePath = if allowRawFileName then ["{RawFileName}"] else []
  180. let searchPaths =
  181. match resolutionEnvironment with
  182. | DesigntimeLike
  183. | RuntimeLike ->
  184. logmessage("Using scripting resolution precedence.")
  185. // These are search paths for runtime-like or scripting resolution. GAC searching is present.
  186. rawFileNamePath @ // Quick-resolve straight to filename first
  187. explicitIncludeDirs @ // From -I, #I
  188. [implicitIncludeDir] @ // Usually the project directory
  189. [fsharpBinariesDir] @ // Location of fsc.exe
  190. ["{TargetFrameworkDirectory}"] @
  191. [sprintf "{Registry:%s,%s,%s%s}" frameworkRegistryBase targetFrameworkVersion assemblyFoldersSuffix assemblyFoldersConditions] @
  192. ["{AssemblyFolders}"] @
  193. ["{GAC}"]
  194. | CompileTimeLike ->
  195. logmessage("Using compilation resolution precedence.")
  196. // These are search paths for compile-like resolution. GAC searching is not present.
  197. ["{TargetFrameworkDirectory}"] @
  198. rawFileNamePath @ // Quick-resolve straight to filename first
  199. explicitIncludeDirs @ // From -I, #I
  200. [implicitIncludeDir] @ // Usually the project directory
  201. [fsharpBinariesDir] @ // Location of fsc.exe
  202. [sprintf "{Registry:%s,%s,%s%s}" frameworkRegistryBase targetFrameworkVersion assemblyFoldersSuffix assemblyFoldersConditions] @ // Like {Registry:Software\Microsoft\.NETFramework,v2.0,AssemblyFoldersEx}
  203. ["{AssemblyFolders}"] @
  204. [outputDirectory]
  205. rar.SearchPaths <- searchPaths |> Array.ofList
  206. rar.AllowedAssemblyExtensions <- [| ".exe"; ".dll" |]
  207. let succeeded = rar.Execute()
  208. // Unroll any foregrounded messages
  209. match !backgroundException with
  210. | Some(backGroundException) ->
  211. logwarning "" "Saw error on logger thread during resolution."
  212. logwarning "" (sprintf "%A" backGroundException)
  213. logwarning "" "Showing messages seen after exception."
  214. !foregrounded
  215. |> List.iter(fun message->
  216. match message with
  217. | ForegroundedMessage(message) -> logmessage message
  218. | ForegroundedWarning(code,message) -> logwarning code message
  219. | ForegroundedError(code,message) -> logerror code message )
  220. | None -> ()
  221. if not succeeded then
  222. raise ResolutionFailure
  223. {
  224. resolvedFiles = [| for p in rar.ResolvedFiles -> {itemSpec = p.ItemSpec;
  225. resolvedFrom = DecodeResolvedFrom(p.GetMetadata("ResolvedFrom"));
  226. fusionName = p.GetMetadata("FusionName");
  227. version = p.GetMetadata("Version");
  228. redist = p.GetMetadata("Redist");
  229. baggage = p.GetMetadata("Baggage") } |]
  230. referenceDependencyPaths = [| for p in rar.ResolvedDependencyFiles -> p.ItemSpec |]
  231. relatedPaths = [| for p in rar.RelatedFiles -> p.ItemSpec |]
  232. referenceSatellitePaths = [| for p in rar.SatelliteFiles -> p.ItemSpec |]
  233. referenceScatterPaths = [| for p in rar.ScatterFiles -> p.ItemSpec |]
  234. referenceCopyLocalPaths = [| for p in rar.CopyLocalFiles -> p.ItemSpec |]
  235. suggestedBindingRedirects = [| for p in rar.SuggestedRedirects -> p.ItemSpec |]
  236. }
  237. let Resolve(
  238. resolutionEnvironment: ResolutionEnvironment,
  239. references:(string*(*baggage*)string)[],
  240. targetFrameworkVersion:string,
  241. targetFrameworkDirectories:string list,
  242. targetProcessorArchitecture:string,
  243. outputDirectory:string,
  244. fsharpBinariesDir:string,
  245. explicitIncludeDirs:string list,
  246. implicitIncludeDir:string,
  247. frameworkRegistryBase:string,
  248. assemblyFoldersSuffix:string,
  249. assemblyFoldersConditions:string,
  250. logmessage:string->unit,
  251. logwarning:ErrorWarningCallbackSig,
  252. logerror:ErrorWarningCallbackSig ) =
  253. // The {RawFileName} target is 'dangerous', in the sense that is uses Directory.GetCurrentDirectory() to resolve unrooted file paths.
  254. // It is unreliable to use this mutable global state inside Visual Studio. As a result, we partition all references into a "rooted" set
  255. // (which contains e.g. C:\MyDir\MyAssem.dll) and "unrooted" (everything else). We only allow "rooted" to use {RawFileName}. Note that
  256. // unrooted may still find 'local' assemblies by virtue of the fact that "implicitIncludeDir" is one of the places searched during
  257. // assembly resolution.
  258. let references = references |> Array.map (fun ((file,baggage) as data) ->
  259. // However, MSBuild will not resolve 'relative' paths, even when e.g. implicitIncludeDir is part of the search. As a result,
  260. // if we have an unrooted path+filename, we'll assume this is relative to the project directory and root it.
  261. if System.IO.Path.IsPathRooted(file) then
  262. data // fine, e.g. "C:\Dir\foo.dll"
  263. elif not(file.Contains("\\") || file.Contains("/")) then
  264. data // fine, e.g. "System.Transactions.dll"
  265. else
  266. // we have a 'relative path', e.g. "bin/Debug/foo.exe" or "..\Yadda\bar.dll"
  267. // turn it into an absolute path based at implicitIncludeDir
  268. (System.IO.Path.Combine(implicitIncludeDir, file), baggage)
  269. )
  270. let rooted, unrooted = references |> Array.partition (fun (file,_baggage) -> System.IO.Path.IsPathRooted(file))
  271. let CallResolveCore(references, allowRawFileName) =
  272. if Array.isEmpty references then
  273. {
  274. resolvedFiles = [| |]
  275. referenceDependencyPaths = [| |]
  276. relatedPaths = [| |]
  277. referenceSatellitePaths = [| |]
  278. referenceScatterPaths = [| |]
  279. referenceCopyLocalPaths = [| |]
  280. suggestedBindingRedirects = [| |]
  281. }
  282. else
  283. // all the params are the same...
  284. ResolveCore(
  285. resolutionEnvironment,
  286. references, // ... except this
  287. targetFrameworkVersion,
  288. targetFrameworkDirectories,
  289. targetProcessorArchitecture,
  290. outputDirectory,
  291. fsharpBinariesDir,
  292. explicitIncludeDirs,
  293. implicitIncludeDir,
  294. frameworkRegistryBase,
  295. assemblyFoldersSuffix,
  296. assemblyFoldersConditions,
  297. allowRawFileName, // ... and this
  298. logmessage,
  299. logwarning,
  300. logerror)
  301. let rootedResults = CallResolveCore(rooted, true)
  302. let unrootedResults = CallResolveCore(unrooted, false)
  303. // now unify the two sets of results
  304. {
  305. resolvedFiles = Array.concat [| rootedResults.resolvedFiles; unrootedResults.resolvedFiles |]
  306. referenceDependencyPaths = set rootedResults.referenceDependencyPaths |> Set.union (set unrootedResults.referenceDependencyPaths) |> Set.toArray
  307. relatedPaths = set rootedResults.relatedPaths |> Set.union (set unrootedResults.relatedPaths) |> Set.toArray
  308. referenceSatellitePaths = set rootedResults.referenceSatellitePaths |> Set.union (set unrootedResults.referenceSatellitePaths) |> Set.toArray
  309. referenceScatterPaths = set rootedResults.referenceScatterPaths |> Set.union (set unrootedResults.referenceScatterPaths) |> Set.toArray
  310. referenceCopyLocalPaths = set rootedResults.referenceCopyLocalPaths |> Set.union (set unrootedResults.referenceCopyLocalPaths) |> Set.toArray
  311. suggestedBindingRedirects = set rootedResults.suggestedBindingRedirects |> Set.union (set unrootedResults.suggestedBindingRedirects) |> Set.toArray
  312. }